]> git.lyx.org Git - lyx.git/blob - src/frontends/xforms/FormFiledialog.C
Change glob() API to accept a dir parameter.
[lyx.git] / src / frontends / xforms / FormFiledialog.C
1 /**
2  * \file FormFiledialog.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author unknown
7  * \author John Levon
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "FormFiledialog.h"
15 #include "forms/form_filedialog.h"
16
17 #include "forms_gettext.h"
18 #include "xforms_helpers.h"
19
20 #include "frontends/Dialogs.h"
21
22 #include "support/FileInfo.h"
23 #include "support/filefilterlist.h"
24 #include "support/filetools.h"
25 #include "support/globbing.h"
26 #include "support/lstrings.h"
27 #include "support/lyxlib.h"
28 #include "support/tostr.h"
29
30 #include "lyx_forms.h"
31
32 #include <boost/bind.hpp>
33 #include <boost/regex.hpp>
34 #include <boost/tokenizer.hpp>
35
36 #include <algorithm>
37 #include <map>
38 #include <sstream>
39
40 #include <grp.h>
41 #include <pwd.h>
42
43 //#ifdef HAVE_ERRNO_H
44 //#include <cerrno>
45 //#endif
46
47 #if HAVE_DIRENT_H
48 # include <dirent.h>
49 #else
50 # define dirent direct
51 # if HAVE_SYS_NDIR_H
52 #  include <sys/ndir.h>
53 # endif
54 # if HAVE_SYS_DIR_H
55 #  include <sys/dir.h>
56 # endif
57 # if HAVE_NDIR_H
58 #  include <ndir.h>
59 # endif
60 #endif
61
62 using lyx::support::AbsolutePath;
63 using lyx::support::AddName;
64 using lyx::support::ExpandPath;
65 using lyx::support::FileFilterList;
66 using lyx::support::FileInfo;
67 using lyx::support::getcwd;
68 using lyx::support::GetEnvPath;
69 using lyx::support::LyXReadLink;
70 using lyx::support::MakeAbsPath;
71 using lyx::support::OnlyFilename;
72 using lyx::support::split;
73 using lyx::support::subst;
74 using lyx::support::suffixIs;
75 using lyx::support::trim;
76
77 using std::max;
78 using std::sort;
79 using std::ostringstream;
80 using std::string;
81 using std::map;
82 using std::vector;
83
84 using namespace lyx::frontend;
85
86 namespace {
87
88 /** Given a string "<glob> <glob> <glob>", expand each glob in turn.
89  *  Any glob that cannot be expanded is ignored silently.
90  *  Invokes \c convert_brace_glob and \c glob internally, so use only
91  *  on systems supporting the Posix function 'glob'.
92  *  \param mask the string "<glob> <glob> <glob>".
93  *  \param directory the current working directory from
94  *  which \c glob is invoked.
95  *  \returns a vector of all matching file names.
96  */
97 vector<string> const expand_globs(string const & mask,
98                                   string const & directory)
99 {
100         // Split into individual globs and then call 'glob' on each one.
101         typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
102         boost::char_separator<char> const separator(" ");
103
104         vector<string> matches;
105         Tokenizer const tokens(mask, separator);
106         Tokenizer::const_iterator it = tokens.begin();
107         Tokenizer::const_iterator const end = tokens.end();
108         for (; it != end; ++it)
109                 lyx::support::glob(matches, *it, directory);
110
111         return matches;
112 }
113
114
115 // six months, in seconds
116 long const SIX_MONTH_SEC = 6L * 30L * 24L * 60L * 60L;
117 //static
118 long const ONE_HOUR_SEC = 60L * 60L;
119
120 extern "C" {
121
122         static
123         int C_LyXFileDlg_CancelCB(FL_FORM *fl, void *xev)
124         {
125                 return FileDialog::Private::CancelCB(fl, xev);
126         }
127
128         static
129         void C_LyXFileDlg_DoubleClickCB(FL_OBJECT * ob, long data)
130         {
131                 FileDialog::Private::DoubleClickCB(ob, data);
132         }
133
134         static
135         void C_LyXFileDlg_FileDlgCB(FL_OBJECT * ob, long data)
136         {
137                 FileDialog::Private::FileDlgCB(ob, data);
138         }
139
140 }
141
142 // *** User cache class implementation
143 /// User cache class definition
144 class UserCache {
145 public:
146         /// seeks user name from group ID
147         string const & find(uid_t ID) const {
148                 Users::const_iterator cit = users.find(ID);
149                 if (cit == users.end()) {
150                         add(ID);
151                         return users[ID];
152                 }
153                 return cit->second;
154         }
155 private:
156         ///
157         void add(uid_t ID) const;
158         ///
159         typedef map<uid_t, string> Users;
160         ///
161         mutable Users users;
162 };
163
164
165 void UserCache::add(uid_t ID) const
166 {
167         struct passwd const * entry = getpwuid(ID);
168         users[ID] = entry ? entry->pw_name : tostr(int(ID));
169 }
170
171
172 /// Group cache class definition
173 class GroupCache {
174 public:
175         /// seeks group name from group ID
176         string const & find(gid_t ID) const ;
177 private:
178         ///
179         void add(gid_t ID) const;
180         ///
181         typedef map<gid_t, string> Groups;
182         ///
183         mutable Groups groups;
184 };
185
186
187 string const & GroupCache::find(gid_t ID) const
188 {
189         Groups::const_iterator cit = groups.find(ID);
190         if (cit == groups.end()) {
191                 add(ID);
192                 return groups[ID];
193         }
194         return cit->second;
195 }
196
197
198 void GroupCache::add(gid_t ID) const
199 {
200         struct group const * entry = getgrgid(ID);
201         groups[ID] = entry ? entry->gr_name : tostr(int(ID));
202 }
203
204 // local instances
205 UserCache lyxUserCache;
206 GroupCache lyxGroupCache;
207
208 // compares two LyXDirEntry objects content (used for sort)
209 class comp_direntry : public std::binary_function<DirEntry, DirEntry, bool> {
210 public:
211         bool operator()(DirEntry const & r1, DirEntry const & r2) const
212         {
213                 bool const r1d = suffixIs(r1.name_, '/');
214                 bool const r2d = suffixIs(r2.name_, '/');
215                 if (r1d && !r2d)
216                         return true;
217                 if (!r1d && r2d)
218                         return false;
219                 return r1.name_ < r2.name_;
220         }
221 };
222
223 } // namespace anon
224
225
226
227 // *** FileDialog::Private class implementation
228
229 // static members
230 FD_filedialog * FileDialog::Private::file_dlg_form_ = 0;
231 FileDialog::Private * FileDialog::Private::current_dlg_ = 0;
232 int FileDialog::Private::minw_ = 0;
233 int FileDialog::Private::minh_ = 0;
234
235
236 // Reread: updates dialog list to match class directory
237 void FileDialog::Private::Reread()
238 {
239         // Opens directory
240         DIR * dir = ::opendir(directory_.c_str());
241         if (!dir) {
242 // FIXME: re-add ...
243 #if 0
244                 Alert::err_alert(_("Warning! Couldn't open directory."),
245                         directory_);
246 #endif
247                 directory_ = getcwd();
248                 dir = ::opendir(directory_.c_str());
249         }
250
251         // Clear the present namelist
252         dir_entries_.clear();
253
254         // Updates display
255         fl_hide_object(file_dlg_form_->List);
256         fl_clear_browser(file_dlg_form_->List);
257         fl_set_input(file_dlg_form_->DirBox, directory_.c_str());
258
259         // Splits complete directory name into directories and compute depth
260         depth_ = 0;
261         string line, Temp;
262         string mode;
263         string File = directory_;
264         if (File != "/")
265                 File = split(File, Temp, '/');
266
267         while (!File.empty() || !Temp.empty()) {
268                 string dline = "@b" + line + Temp + '/';
269                 fl_add_browser_line(file_dlg_form_->List, dline.c_str());
270                 File = split(File, Temp, '/');
271                 line += ' ';
272                 ++depth_;
273         }
274
275         vector<string> const glob_matches = expand_globs(mask_, directory_);
276
277         time_t curTime = time(0);
278         rewinddir(dir);
279         while (dirent * entry = readdir(dir)) {
280                 bool isLink = false, isDir = false;
281
282                 // If the pattern doesn't start with a dot, skip hidden files
283                 if (!mask_.empty() && mask_[0] != '.' &&
284                     entry->d_name[0] == '.')
285                         continue;
286
287                 // Gets filename
288                 string fname = entry->d_name;
289
290                 // Under all circumstances, "." and ".." are not wanted
291                 if (fname == "." || fname == "..")
292                         continue;
293
294                 // gets file status
295                 File = AddName(directory_, fname);
296
297                 FileInfo fileInfo(File, true);
298
299                 // can this really happen?
300                 if (!fileInfo.isOK())
301                         continue;
302
303                 mode = fileInfo.modeString();
304                 unsigned int const nlink = fileInfo.getNumberOfLinks();
305                 string const user  = lyxUserCache.find(fileInfo.getUid());
306                 string const group = lyxGroupCache.find(fileInfo.getGid());
307
308                 time_t modtime = fileInfo.getModificationTime();
309                 string Time = ctime(&modtime);
310
311                 if (curTime > modtime + SIX_MONTH_SEC
312                     || curTime < modtime + ONE_HOUR_SEC) {
313                         // The file is fairly old or in the future. POSIX says
314                         // the cutoff is 6 months old. Allow a 1 hour slop
315                         // factor for what is considered "the future", to
316                         // allow for NFS server/client clock disagreement.
317                         // Show the year instead of the time of day.
318                         Time.erase(10, 9);
319                         Time.erase(15, string::npos);
320                 } else {
321                         Time.erase(16, string::npos);
322                 }
323
324                 string buffer = mode + ' ' +
325                         tostr(nlink) + ' ' +
326                         user + ' ' +
327                         group + ' ' +
328                         Time.substr(4, string::npos) + ' ';
329
330                 buffer += entry->d_name;
331                 buffer += fileInfo.typeIndicator();
332
333                 isLink = fileInfo.isLink();
334                 if (isLink) {
335                         string Link;
336
337                         if (LyXReadLink(File, Link)) {
338                                 buffer += " -> ";
339                                 buffer += Link;
340
341                                 // This gives the FileType of the file that
342                                 // is really pointed to after resolving all
343                                 // symlinks. This is not necessarily the same
344                                 // as the type of Link (which could again be a
345                                 // link). Is that intended?
346                                 //                              JV 199902
347                                 fileInfo.newFile(File);
348                                 if (fileInfo.isOK())
349                                         buffer += fileInfo.typeIndicator();
350                                 else
351                                         continue;
352                         }
353                 }
354
355                 // filters files according to pattern and type
356                 if (fileInfo.isRegular()
357                     || fileInfo.isChar()
358                     || fileInfo.isBlock()
359                     || fileInfo.isFifo()) {
360                         typedef vector<string>::const_iterator viterator;
361                         viterator gbegin = glob_matches.begin();
362                         viterator const gend = glob_matches.end();
363                         if (std::find(gbegin, gend, fname) == gend)
364                                 continue;
365                 } else if (!(isDir = fileInfo.isDir()))
366                         continue;
367
368                 DirEntry tmp;
369
370                 // Note ls_entry_ is an string!
371                 tmp.ls_entry_ = buffer;
372                 // creates used name
373                 string temp = fname;
374                 if (isDir)
375                         temp += '/';
376
377                 tmp.name_ = temp;
378                 // creates displayed name
379                 temp = entry->d_name;
380                 if (isLink)
381                         temp += '@';
382                 else
383                         temp += fileInfo.typeIndicator();
384                 tmp.displayed_ = temp;
385
386                 dir_entries_.push_back(tmp);
387         }
388
389         closedir(dir);
390
391         // Sort the names
392         sort(dir_entries_.begin(), dir_entries_.end(), comp_direntry());
393
394         // Add them to directory box
395         for (DirEntries::const_iterator cit = dir_entries_.begin();
396              cit != dir_entries_.end(); ++cit) {
397                 string const temp = line + cit->displayed_;
398                 fl_add_browser_line(file_dlg_form_->List, temp.c_str());
399         }
400         fl_set_browser_topline(file_dlg_form_->List, depth_);
401         fl_show_object(file_dlg_form_->List);
402         last_sel_ = -1;
403 }
404
405
406 // SetDirectory: sets dialog current directory
407 void FileDialog::Private::SetDirectory(string const & path)
408 {
409         string tmp;
410         if (path.empty())
411                 tmp = getcwd();
412         else
413                 tmp = MakeAbsPath(ExpandPath(path), directory_);
414
415         // must check the directory exists
416         DIR * dir = ::opendir(tmp.c_str());
417         if (!dir) {
418 // FIXME: re-add ...
419 #if 0
420                 Alert::err_alert(_("Warning! Couldn't open directory."), tmp);
421 #endif
422         } else {
423                 ::closedir(dir);
424                 directory_ = tmp;
425         }
426 }
427
428
429 void FileDialog::Private::SetFilters(string const & mask)
430 {
431         SetFilters(FileFilterList(mask));
432 }
433
434
435 void FileDialog::Private::SetFilters(FileFilterList const & filters)
436 {
437         if (filters.empty())
438                 return;
439
440         // Just take the first one for now.
441         typedef FileFilterList::Filter::glob_iterator glob_iterator;
442         glob_iterator const begin = filters[0].begin();
443         glob_iterator const end = filters[0].end();
444         if (begin == end)
445                 return;
446
447         ostringstream ss;
448         for (glob_iterator it = begin; it != end; ++it) {
449                 if (it != begin)
450                         ss << ' ';
451                 ss << *it;
452         }
453
454         mask_ = ss.str();
455         fl_set_input(file_dlg_form_->PatBox, mask_.c_str());
456 }
457
458
459 // SetInfoLine: sets dialog information line
460 void FileDialog::Private::SetInfoLine(string const & line)
461 {
462         info_line_ = line;
463         fl_set_object_label(file_dlg_form_->FileInfo, info_line_.c_str());
464 }
465
466
467 FileDialog::Private::Private()
468 {
469         directory_ = MakeAbsPath(string("."));
470
471         // Creates form if necessary.
472         if (!file_dlg_form_) {
473                 file_dlg_form_ = build_filedialog(this);
474                 minw_ = file_dlg_form_->form->w;
475                 minh_ = file_dlg_form_->form->h;
476                 // Set callbacks. This means that we don't need a patch file
477                 fl_set_object_callback(file_dlg_form_->DirBox,
478                                        C_LyXFileDlg_FileDlgCB, 0);
479                 fl_set_object_callback(file_dlg_form_->PatBox,
480                                        C_LyXFileDlg_FileDlgCB, 1);
481                 fl_set_object_callback(file_dlg_form_->List,
482                                        C_LyXFileDlg_FileDlgCB, 2);
483                 fl_set_object_callback(file_dlg_form_->Filename,
484                                        C_LyXFileDlg_FileDlgCB, 3);
485                 fl_set_object_callback(file_dlg_form_->Rescan,
486                                        C_LyXFileDlg_FileDlgCB, 10);
487                 fl_set_object_callback(file_dlg_form_->Home,
488                                        C_LyXFileDlg_FileDlgCB, 11);
489                 fl_set_object_callback(file_dlg_form_->User1,
490                                        C_LyXFileDlg_FileDlgCB, 12);
491                 fl_set_object_callback(file_dlg_form_->User2,
492                                        C_LyXFileDlg_FileDlgCB, 13);
493
494                 // Make sure pressing the close box doesn't crash LyX. (RvdK)
495                 fl_set_form_atclose(file_dlg_form_->form,
496                                     C_LyXFileDlg_CancelCB, 0);
497                 // Register doubleclick callback
498                 fl_set_browser_dblclick_callback(file_dlg_form_->List,
499                                                  C_LyXFileDlg_DoubleClickCB,
500                                                  0);
501         }
502         fl_hide_object(file_dlg_form_->User1);
503         fl_hide_object(file_dlg_form_->User2);
504
505         r_ = Dialogs::redrawGUI().connect(boost::bind(&FileDialog::Private::redraw, this));
506 }
507
508
509 FileDialog::Private::~Private()
510 {
511         r_.disconnect();
512 }
513
514
515 void FileDialog::Private::redraw()
516 {
517         if (file_dlg_form_->form && file_dlg_form_->form->visible)
518                 fl_redraw_form(file_dlg_form_->form);
519 }
520
521
522 // SetButton: sets file selector user button action
523 void FileDialog::Private::SetButton(int index, string const & name,
524                            string const & path)
525 {
526         FL_OBJECT * ob;
527         string * tmp;
528
529         if (index == 0) {
530                 ob = file_dlg_form_->User1;
531                 tmp = &user_path1_;
532         } else if (index == 1) {
533                 ob = file_dlg_form_->User2;
534                 tmp = &user_path2_;
535         } else {
536                 return;
537         }
538
539         if (!name.empty()) {
540                 fl_set_object_label(ob, idex(name).c_str());
541                 fl_set_button_shortcut(ob, scex(name).c_str(), 1);
542                 fl_show_object(ob);
543                 *tmp = path;
544         } else {
545                 fl_hide_object(ob);
546                 tmp->erase();
547         }
548 }
549
550
551 // GetDirectory: gets last dialog directory
552 string const FileDialog::Private::GetDirectory() const
553 {
554         if (!directory_.empty())
555                 return directory_;
556         else
557                 return string(".");
558 }
559
560 namespace {
561         bool x_sync_kludge(bool ret)
562         {
563                 XSync(fl_get_display(), false);
564                 return ret;
565         }
566 } // namespace anon
567
568 // RunDialog: handle dialog during file selection
569 bool FileDialog::Private::RunDialog()
570 {
571         force_cancel_ = false;
572         force_ok_ = false;
573
574         // event loop
575         while (true) {
576                 FL_OBJECT * ob = fl_do_forms();
577
578                 if (ob == file_dlg_form_->Ready) {
579                         if (HandleOK())
580                                 return x_sync_kludge(true);
581
582                 } else if (ob == file_dlg_form_->Cancel || force_cancel_)
583                         return x_sync_kludge(false);
584
585                 else if (force_ok_)
586                         return x_sync_kludge(true);
587         }
588 }
589
590
591 // XForms objects callback (static)
592 void FileDialog::Private::FileDlgCB(FL_OBJECT *, long arg)
593 {
594         if (!current_dlg_)
595                 return;
596
597         switch (arg) {
598
599         case 0: // get directory
600                 current_dlg_->SetDirectory(fl_get_input(file_dlg_form_->DirBox));
601                 current_dlg_->Reread();
602                 break;
603
604         case 1: // get mask
605                 current_dlg_->SetFilters(fl_get_input(file_dlg_form_->PatBox));
606                 current_dlg_->Reread();
607                 break;
608
609         case 2: // list
610                 current_dlg_->HandleListHit();
611                 break;
612
613         case 10: // rescan
614                 current_dlg_->SetDirectory(fl_get_input(file_dlg_form_->DirBox));
615                 current_dlg_->SetFilters(fl_get_input(file_dlg_form_->PatBox));
616                 current_dlg_->Reread();
617                 break;
618
619         case 11: // home
620                 current_dlg_->SetDirectory(GetEnvPath("HOME"));
621                 current_dlg_->SetFilters(fl_get_input(file_dlg_form_->PatBox));
622                 current_dlg_->Reread();
623                 break;
624
625         case 12: // user button 1
626                 current_dlg_->SetDirectory(current_dlg_->user_path1_);
627                 current_dlg_->SetFilters(fl_get_input(file_dlg_form_->PatBox));
628                 current_dlg_->Reread();
629                 break;
630
631         case 13: // user button 2
632                 current_dlg_->SetDirectory(current_dlg_->user_path2_);
633                 current_dlg_->SetFilters(fl_get_input(file_dlg_form_->PatBox));
634                 current_dlg_->Reread();
635                 break;
636
637         }
638 }
639
640
641 // Handle callback from list
642 void FileDialog::Private::HandleListHit()
643 {
644         // set info line
645         int const select_ = fl_get_browser(file_dlg_form_->List);
646         if (select_ > depth_)
647                 SetInfoLine(dir_entries_[select_ - depth_ - 1].ls_entry_);
648         else
649                 SetInfoLine(string());
650 }
651
652
653 // Callback for double click in list
654 void FileDialog::Private::DoubleClickCB(FL_OBJECT *, long)
655 {
656         // Simulate click on OK button
657         if (current_dlg_->HandleDoubleClick())
658                 current_dlg_->Force(false);
659 }
660
661
662 // Handle double click from list
663 bool FileDialog::Private::HandleDoubleClick()
664 {
665         string tmp;
666
667         // set info line
668         bool isDir = true;
669         int const select_ = fl_get_browser(file_dlg_form_->List);
670         if (select_ > depth_) {
671                 tmp = dir_entries_[select_ - depth_ - 1].name_;
672                 SetInfoLine(dir_entries_[select_ - depth_ - 1].ls_entry_);
673                 if (!suffixIs(tmp, '/')) {
674                         isDir = false;
675                         fl_set_input(file_dlg_form_->Filename, tmp.c_str());
676                 }
677         } else if (select_ != 0) {
678                 SetInfoLine(string());
679         } else
680                 return true;
681
682         // executes action
683         if (isDir) {
684                 string Temp;
685
686                 // builds new directory name
687                 if (select_ > depth_) {
688                         // Directory deeper down
689                         // First, get directory with trailing /
690                         Temp = fl_get_input(file_dlg_form_->DirBox);
691                         if (!suffixIs(Temp, '/'))
692                                 Temp += '/';
693                         Temp += tmp;
694                 } else {
695                         // Directory higher up
696                         Temp.erase();
697                         for (int i = 0; i < select_; ++i) {
698                                 string piece = fl_get_browser_line(file_dlg_form_->List, i+1);
699                                 // The '+2' is here to count the '@b' (JMarc)
700                                 Temp += piece.substr(i + 2);
701                         }
702                 }
703
704                 // assigns it
705                 SetDirectory(Temp);
706                 Reread();
707                 return false;
708         }
709         return true;
710 }
711
712
713 // Handle OK button call
714 bool FileDialog::Private::HandleOK()
715 {
716         // mask was changed
717         string tmp = fl_get_input(file_dlg_form_->PatBox);
718         if (tmp != mask_) {
719                 SetFilters(tmp);
720                 Reread();
721                 return false;
722         }
723
724         // directory was changed
725         tmp = fl_get_input(file_dlg_form_->DirBox);
726         if (tmp != directory_) {
727                 SetDirectory(tmp);
728                 Reread();
729                 return false;
730         }
731
732         // Handle return from list
733         int const select = fl_get_browser(file_dlg_form_->List);
734         if (select > depth_) {
735                 string const temp = dir_entries_[select - depth_ - 1].name_;
736                 if (!suffixIs(temp, '/')) {
737                         // If user didn't type anything, use browser
738                         string const name = fl_get_input(file_dlg_form_->Filename);
739                         if (name.empty())
740                                 fl_set_input(file_dlg_form_->Filename, temp.c_str());
741                         return true;
742                 }
743         }
744
745         // Emulate a doubleclick
746         return HandleDoubleClick();
747 }
748
749
750 // Handle Cancel CB from WM close
751 int FileDialog::Private::CancelCB(FL_FORM *, void *)
752 {
753         // Simulate a click on the cancel button
754         current_dlg_->Force(true);
755         return FL_IGNORE;
756 }
757
758
759 // Simulates a click on OK/Cancel
760 void FileDialog::Private::Force(bool cancel)
761 {
762         if (cancel) {
763                 force_cancel_ = true;
764                 fl_set_button(file_dlg_form_->Cancel, 1);
765         } else {
766                 force_ok_ = true;
767                 fl_set_button(file_dlg_form_->Ready, 1);
768         }
769         // Start timer to break fl_do_forms loop soon
770         fl_set_timer(file_dlg_form_->timer, 0.1);
771 }
772
773
774 // Select: launches dialog and returns selected file
775 string const FileDialog::Private::Select(string const & title,
776                                          string const & path,
777                                          FileFilterList const & filters,
778                                          string const & suggested)
779 {
780         // handles new mask and path
781         SetFilters(filters);
782         SetDirectory(path);
783         Reread();
784
785         // highlight the suggested file in the browser, if it exists.
786         int sel = 0;
787         string const filename = OnlyFilename(suggested);
788         if (!filename.empty()) {
789                 for (int i = 0; i < fl_get_browser_maxline(file_dlg_form_->List); ++i) {
790                         string s = fl_get_browser_line(file_dlg_form_->List, i + 1);
791                         s = trim(s);
792                         if (s == filename) {
793                                 sel = i + 1;
794                                 break;
795                         }
796                 }
797         }
798
799         if (sel != 0)
800                 fl_select_browser_line(file_dlg_form_->List, sel);
801         int const top = max(sel - 5, 1);
802         fl_set_browser_topline(file_dlg_form_->List, top);
803
804         // checks whether dialog can be started
805         if (current_dlg_)
806                 return string();
807         current_dlg_ = this;
808
809         // runs dialog
810         SetInfoLine(string());
811         setEnabled(file_dlg_form_->Filename, true);
812         fl_set_input(file_dlg_form_->Filename, suggested.c_str());
813         fl_set_button(file_dlg_form_->Cancel, 0);
814         fl_set_button(file_dlg_form_->Ready, 0);
815         fl_set_focus_object(file_dlg_form_->form, file_dlg_form_->Filename);
816         fl_deactivate_all_forms();
817         // Prevent xforms crashing if the dialog gets too small by preventing
818         // it from being shrunk beyond a minimum size.
819         // calls to fl_set_form_minsize/maxsize apply only to the next
820         // fl_show_form(), so this comes first.
821         fl_set_form_minsize(file_dlg_form_->form, minw_, minh_);
822
823         fl_show_form(file_dlg_form_->form,
824                      FL_PLACE_MOUSE | FL_FREE_SIZE, 0,
825                      title.c_str());
826
827         bool const isOk = RunDialog();
828
829         fl_hide_form(file_dlg_form_->form);
830         fl_activate_all_forms();
831         current_dlg_ = 0;
832
833         // Returns filename or string() if no valid selection was made
834         if (!isOk || !fl_get_input(file_dlg_form_->Filename)[0])
835                 return string();
836
837         file_name_ = fl_get_input(file_dlg_form_->Filename);
838
839         if (!AbsolutePath(file_name_))
840                 file_name_ = AddName(fl_get_input(file_dlg_form_->DirBox), file_name_);
841         return file_name_;
842 }
843
844
845 // SelectDir: launches dialog and returns selected directory
846 string const FileDialog::Private::SelectDir(string const & title,
847                                          string const & path,
848                                          string const & suggested)
849 {
850         SetFilters("*/");
851         // handles new path
852         bool isOk = true;
853         if (!path.empty()) {
854                 // handle case where path does not end with "/"
855                 // remerge path+suggested and check if it is a valid path
856                 if (!suggested.empty()) {
857                         string tmp = suggested;
858                         if (!suffixIs(tmp, '/'))
859                                 tmp += '/';
860                         string full_path = path;
861                         full_path += tmp;
862                         // check if this is really a directory
863                         DIR * dir = ::opendir(full_path.c_str());
864                         if (dir)
865                                 SetDirectory(full_path);
866                         else
867                                 SetDirectory(path);
868                 } else
869                         SetDirectory(path);
870                 isOk = false;
871         }
872         if (!isOk)
873                 Reread();
874
875         // checks whether dialog can be started
876         if (current_dlg_)
877                 return string();
878         current_dlg_ = this;
879
880         // runs dialog
881         SetInfoLine(string());
882         fl_set_input(file_dlg_form_->Filename, "");
883         setEnabled(file_dlg_form_->Filename, false);
884         fl_set_button(file_dlg_form_->Cancel, 0);
885         fl_set_button(file_dlg_form_->Ready, 0);
886         fl_set_focus_object(file_dlg_form_->form, file_dlg_form_->DirBox);
887         fl_deactivate_all_forms();
888         fl_show_form(file_dlg_form_->form,
889                      FL_PLACE_MOUSE | FL_FREE_SIZE, 0,
890                      title.c_str());
891
892         isOk = RunDialog();
893
894         fl_hide_form(file_dlg_form_->form);
895         fl_activate_all_forms();
896         current_dlg_ = 0;
897
898         // Returns directory or string() if no valid selection was made
899         if (!isOk)
900                 return string();
901
902         file_name_ = fl_get_input(file_dlg_form_->DirBox);
903         return file_name_;
904 }