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