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