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