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