This commit was manufactured by cvs2svn to create tag
[lyx.git] / src / filedlg.C
blobbbd63ba7ed6809cf7285ec94dc8b5752fe18c739
1 // -*- C++ -*-
2 /* This file is part of
3  * ======================================================
4  * 
5  *           LyX, The Document Processor
6  *        
7  *           Copyright 1995 Matthias Ettrich
8  *           Copyright 1995-1999 The LyX Team.
9  *
10  * ======================================================*/
12 #include <config.h>
14 #include <unistd.h>
15 #include <cstdio>
16 #include <cstdlib>
17 #include <pwd.h>
18 #include <grp.h>
19 #include <cstring>
21 #include "lyx_gui_misc.h" // CancelCloseCB
22 #include "support/FileInfo.h"
23 #include "gettext.h"
25 #ifdef HAVE_ERRNO_H
26 #include <cerrno>
27 #endif
29 #if HAVE_DIRENT_H
30 # include <dirent.h>
31 # define NAMLEN(dirent) strlen((dirent)->d_name)
32 #else
33 # define dirent direct
34 # define NAMLEN(dirent) (dirent)->d_namlen
35 # if HAVE_SYS_NDIR_H
36 #  include <sys/ndir.h>
37 # endif
38 # if HAVE_SYS_DIR_H
39 #  include <sys/dir.h>
40 # endif
41 # if HAVE_NDIR_H
42 #  include <ndir.h>
43 # endif
44 #endif
46 #if TIME_WITH_SYS_TIME
47 # include <sys/time.h>
48 # include <ctime>
49 #else
50 # if HAVE_SYS_TIME_H
51 #  include <sys/time.h>
52 # else
53 #  include <ctime>
54 # endif
55 #endif
57 #ifdef BROKEN_HEADERS
58 extern "C" int gettimeofday(struct timeval *,struct timezone *);
59 #define remove(a) unlink(a)      
60 #endif
62 #ifdef __GNUG__
63 #pragma implementation
64 #endif
66 #include "support/filetools.h"
67 #include "filedlg.h"
68 #include "definitions.h"
70 static const long SIX_MONTH_SEC = 6L * 30L * 24L * 60L * 60L; // six months, in seconds
71 static const long ONE_HOUR_SEC = 60L * 60L;
73 // *** User cache class implementation
75 // global instance (user cache root)
76 UserCache lyxUserCache = UserCache(string(),0,0);
78 // some "C" wrappers around callbacks
79 extern "C" void C_LyXFileDlg_FileDlgCB(FL_OBJECT *, long lArgument);
80 extern "C" void C_LyXFileDlg_DoubleClickCB(FL_OBJECT *, long);
81 extern "C" int C_LyXFileDlg_CancelCB(FL_FORM *, void *);
82 extern "C" int C_LyXDirEntryC_ldeCompProc(const void* r1, 
83                                           const void* r2);
85 // Add: creates a new user entry
86 UserCache * UserCache::Add(uid_t ID)
88         string pszNewName;
89         struct passwd * pEntry;
91         // gets user name
92         if ((pEntry = getpwuid(ID)))
93                 pszNewName = pEntry->pw_name;
94         else {
95                 pszNewName = tostr(ID);
96         }
98         // adds new node
99         return new UserCache(pszNewName, ID, pRoot);
103 UserCache::UserCache(string const & pszName, uid_t ID, UserCache * pRoot)
105         // links node
106         if (pRoot) {
107                 this->pRoot = pRoot;
108                 pNext = pRoot->pNext;
109                 pRoot->pNext = this;
110         } else {
111                 this->pRoot = this;
112                 pNext = 0;
113         }
115         // stores data
116         this->pszName = pszName;
117         this->ID = ID;
121 UserCache::~UserCache()
123         if (pNext) delete pNext;
127 // Find: seeks user name from user ID
128 string UserCache::Find(uid_t ID)
130         if ((!pszName.empty()) && (this->ID == ID)) return pszName; 
131         if (pNext) return pNext->Find(ID);
133         return pRoot->Add(ID)->pszName;
137 // *** Group cache class implementation
139 // global instance (group cache root)
140 GroupCache lyxGroupCache = GroupCache(string(),0,0);
142 // Add: creates a new group entry
143 GroupCache * GroupCache::Add(gid_t ID)
145         string pszNewName;
146         struct group * pEntry;
148         // gets user name
149         if ((pEntry = getgrgid(ID))) pszNewName = pEntry->gr_name;
150         else {
151                 pszNewName = tostr(ID);
152         }
154         // adds new node
155         return new GroupCache(pszNewName, ID, pRoot);
159 GroupCache::GroupCache(string const & pszName, gid_t ID, GroupCache * pRoot)
161         // links node
162         if (pRoot) {
163                 this->pRoot = pRoot;
164                 pNext = pRoot->pNext;
165                 pRoot->pNext = this;
166         } else {
167                 this->pRoot = this;
168                 pNext = 0;
169         }
171         // stores data
172         this->pszName = pszName;
173         this->ID = ID;
177 GroupCache::~GroupCache()
179         if (pNext) delete pNext;
183 // Find: seeks group name from group ID
184 string GroupCache::Find(gid_t ID)
186         if ((!pszName.empty()) && (this->ID == ID)) return pszName; 
187         if (pNext) return pNext->Find(ID);
189         return pRoot->Add(ID)->pszName;
192 // *** LyXDirEntry internal structure implementation
194 // ldeCompProc: compares two LyXDirEntry objects content (used for qsort)
195 int LyXDirEntry::ldeCompProc(const LyXDirEntry * r1, 
196                              const LyXDirEntry * r2)
198         bool r1d = suffixIs(r1->pszName, '/'); 
199         bool r2d = suffixIs(r2->pszName, '/');
200         if (r1d && !r2d) return -1;
201         if (!r1d && r2d) return 1;
202         return r1->pszName.compare(r2->pszName);
205 extern "C" int C_LyXDirEntry_ldeCompProc(const void * r1, 
206                                          const void * r2)
208         return LyXDirEntry::ldeCompProc((const LyXDirEntry *)r1,
209                                         (const LyXDirEntry *)r2);
212 // *** LyXFileDlg class implementation
214 // static members
215 FD_FileDlg * LyXFileDlg::pFileDlgForm = 0;
216 LyXFileDlg * LyXFileDlg::pCurrentDlg = 0;
219 // Reread: updates dialog list to match class directory
220 void LyXFileDlg::Reread()
222         int i;
223         DIR * pDirectory;
224         struct dirent * pDirEntry;
225         string File;
226         string Buffer;
227         string Time;
228         char szMode[15];
229         FileInfo fileInfo;
230         
231         // Opens directory
232         pDirectory = opendir(pszDirectory.c_str());
233         if (!pDirectory) {
234                 WriteFSAlert(_("Warning! Couldn't open directory."), 
235                              pszDirectory);
236                 pszDirectory = GetCWD();
237                 pDirectory = opendir(pszDirectory.c_str());
238         }
240         // Clear the present namelist
241         if (pCurrentNames) {
242                 delete [] pCurrentNames;
243                 pCurrentNames = 0;
244         }
246         // Updates display
247         fl_hide_object(pFileDlgForm->List);
248         fl_clear_browser(pFileDlgForm->List);
249         fl_set_input(pFileDlgForm->DirBox, pszDirectory.c_str());
251         // Splits complete directory name into directories and compute depth
252         iDepth = 0;
253         string line, Temp;
254         File = pszDirectory;
255         if (File != "/") {
256                 File = split(File, Temp, '/');
257         }
258         while (!File.empty() || !Temp.empty()) {
259                 string dline = "@b"+line + Temp + '/';          
260                 fl_add_browser_line(pFileDlgForm->List, dline.c_str());
261                 File = split(File, Temp, '/');
262                 line += ' ';
263                 ++iDepth;
264         }
266         // Allocate names array
267         iNumNames = 0;
268         rewinddir(pDirectory);
269         while ((readdir(pDirectory))) ++iNumNames;
270         pCurrentNames = new LyXDirEntry[iNumNames];
272         // Parses all entries of the given subdirectory
273         iNumNames = 0;
274         time_t curTime = time(0);
275         rewinddir(pDirectory);
276         while ((pDirEntry = readdir(pDirectory))) {
278                 bool isLink = false, isDir = false;
280                 // If the pattern doesn't start with a dot, skip hidden files
281                 if (!pszMask.empty() && pszMask[0] != '.' && 
282                     pDirEntry->d_name[0] == '.')
283                         continue;
285                 // Gets filename
286                 string fname = pDirEntry->d_name;
288                 // Under all circumstances, "." and ".." are not wanted
289                 if (fname == "." || fname == "..")
290                         continue;
292                 // gets file status
293                 File = AddName(pszDirectory, fname);
295                 fileInfo.newFile(File, true);
296                 fileInfo.modeString(szMode);
297                 unsigned int nlink = fileInfo.getNumberOfLinks();
298                 string user =   lyxUserCache.Find(fileInfo.getUid());
299                 string group = lyxGroupCache.Find(fileInfo.getGid());
301                 time_t modtime = fileInfo.getModificationTime();
302                 Time = ctime(&modtime);
303                 
304                 if (curTime > fileInfo.getModificationTime() + SIX_MONTH_SEC
305                     || curTime < fileInfo.getModificationTime()
306                     + ONE_HOUR_SEC) {
307                         // The file is fairly old or in the future. POSIX says
308                         // the cutoff is 6 months old. Allow a 1 hour slop
309                         // factor for what is considered "the future", to
310                         // allow for NFS server/client clock disagreement.
311                         // Show the year instead of the time of day.
312                         Time.erase(10, 9);
313                         Time.erase(15, string::npos);
314                 } else {
315                         Time.erase(16, string::npos);
316                 }
318                 Buffer = string(szMode) + ' ' +
319                         tostr(nlink) + ' ' +
320                         user + ' ' +
321                         group + ' ' +
322                         Time.substr(4, string::npos) + ' ';
324                 Buffer += pDirEntry->d_name;
325                 Buffer += fileInfo.typeIndicator();
327                 if ((isLink = fileInfo.isLink())) {
328                   string Link;
330                   if (LyXReadLink(File,Link)) {
331                        Buffer += " -> ";
332                        Buffer += Link;
334                        // This gives the FileType of the file that
335                        // is really pointed too after resolving all
336                        // symlinks. This is not necessarily the same
337                        // as the type of Link (which could again be a
338                        // link). Is that intended?
339                        //                              JV 199902
340                        fileInfo.newFile(File);
341                        Buffer += fileInfo.typeIndicator();
342                   }
343                 }
345                 // filters files according to pattern and type
346                 if (fileInfo.isRegular()
347                     || fileInfo.isChar()
348                     || fileInfo.isBlock()
349                     || fileInfo.isFifo()) {
350                         if (!regexMatch(fname, pszMask))
351                                 continue;
352                 } else if (!(isDir = fileInfo.isDir()))
353                         continue;
355                 // Note pszLsEntry is an string!
356                 pCurrentNames[iNumNames].pszLsEntry = Buffer;
358                 // creates used name
359                 string temp = fname;
360                 if (isDir) temp += '/';
361                 pCurrentNames[iNumNames].pszName = temp;
363                 // creates displayed name
364                 temp = pDirEntry->d_name;
365                 if (isLink)
366                         temp += '@';
367                 else
368                         temp += fileInfo.typeIndicator();
369                 
370                 pCurrentNames[iNumNames++].pszDisplayed = temp;
371         }
373         closedir(pDirectory);
375         // Sort the names
376         qsort(pCurrentNames, iNumNames, sizeof(LyXDirEntry), 
377               C_LyXDirEntry_ldeCompProc);
379         // Add them to directory box
380         for (i = 0; i < iNumNames; ++i) {
381                 string temp = line + pCurrentNames[i].pszDisplayed;
382                 fl_add_browser_line(pFileDlgForm->List, temp.c_str());
383         }
384         fl_set_browser_topline(pFileDlgForm->List,iDepth);
385         fl_show_object(pFileDlgForm->List);
386         iLastSel = -1;
390 // SetDirectory: sets dialog current directory
391 void LyXFileDlg::SetDirectory(string const & Path)
393         if (!pszDirectory.empty()) {
394                 string TempPath = ExpandPath(Path); // Expand ~/
395                 TempPath = MakeAbsPath(TempPath, pszDirectory);
396                 pszDirectory = MakeAbsPath(TempPath);
397         } else pszDirectory = MakeAbsPath(Path);
401 // SetMask: sets dialog file mask
402 void LyXFileDlg::SetMask(string const & NewMask)
404         pszMask = NewMask;
405         fl_set_input(pFileDlgForm->PatBox, pszMask.c_str());
409 // SetInfoLine: sets dialog information line
410 void LyXFileDlg::SetInfoLine(string const & Line)
412         pszInfoLine = Line;
413         fl_set_object_label(pFileDlgForm->FileInfo, pszInfoLine.c_str());
417 LyXFileDlg::LyXFileDlg()
419         pCurrentNames = 0;
420         pszDirectory = MakeAbsPath(string("."));
421         pszMask = '*';
423         // Creates form if necessary. 
424         if (!pFileDlgForm) {
425                 pFileDlgForm = create_form_FileDlg();
426                 // Set callbacks. This means that we don't need a patch file
427                 fl_set_object_callback(pFileDlgForm->DirBox,
428                                        C_LyXFileDlg_FileDlgCB,0);
429                 fl_set_object_callback(pFileDlgForm->PatBox,
430                                        C_LyXFileDlg_FileDlgCB,1);
431                 fl_set_object_callback(pFileDlgForm->List,
432                                        C_LyXFileDlg_FileDlgCB,2);
433                 fl_set_object_callback(pFileDlgForm->Filename,
434                                        C_LyXFileDlg_FileDlgCB,3);
435                 fl_set_object_callback(pFileDlgForm->Rescan,
436                                        C_LyXFileDlg_FileDlgCB,10);
437                 fl_set_object_callback(pFileDlgForm->Home,
438                                        C_LyXFileDlg_FileDlgCB,11);
439                 fl_set_object_callback(pFileDlgForm->User1,
440                                        C_LyXFileDlg_FileDlgCB,12);
441                 fl_set_object_callback(pFileDlgForm->User2,
442                                        C_LyXFileDlg_FileDlgCB,13);
443                 
444                 // Make sure pressing the close box doesn't crash LyX. (RvdK)
445                 fl_set_form_atclose(pFileDlgForm->FileDlg, 
446                                     C_LyXFileDlg_CancelCB, 0);
447                 // Register doubleclick callback
448                 fl_set_browser_dblclick_callback(pFileDlgForm->List,
449                                                  C_LyXFileDlg_DoubleClickCB,0);
450         }
451         fl_hide_object(pFileDlgForm->User1);
452         fl_hide_object(pFileDlgForm->User2);
456 LyXFileDlg::~LyXFileDlg()
458         // frees directory entries
459         if (pCurrentNames) {
460                 delete [] pCurrentNames;
461         }
465 // SetButton: sets file selector user button action
466 void LyXFileDlg::SetButton(int iIndex, string const & pszName, 
467                            string const & pszPath)
469         FL_OBJECT *pObject;
470         string *pTemp;
472         if (iIndex == 0) {
473                 pObject = pFileDlgForm->User1;
474                 pTemp = &pszUserPath1;
475         } else if (iIndex == 1) {                       
476                 pObject = pFileDlgForm->User2;
477                 pTemp = &pszUserPath2;
478         } else return;
480         if (!pszName.empty() && !pszPath.empty()) {
481                 fl_set_object_label(pObject, pszName.c_str());
482                 fl_show_object(pObject);
483                 *pTemp = pszPath;
484         } else {
485                 fl_hide_object(pObject);
486                 (*pTemp).clear();
487         }
491 // GetDirectory: gets last dialog directory
492 string LyXFileDlg::GetDirectory() 
494         if (!pszDirectory.empty())
495                 return pszDirectory;
496         else
497                 return string(".");
501 // RunDialog: handle dialog during file selection
502 bool LyXFileDlg::RunDialog()
504         force_cancel = false;
505         force_ok = false;
506         
507         // event loop
508         while(true) {
510                 FL_OBJECT * pObject = fl_do_forms();
512                 if (pObject == pFileDlgForm->Ready) {
513                         if (HandleOK())
514                                 return true;
515                 } else if (pObject == pFileDlgForm->Cancel 
516                            || force_cancel) 
517                         return false;
518                 else if (force_ok)
519                         return true;
520         }
524 // XForms objects callback (static)
525 void LyXFileDlg::FileDlgCB(FL_OBJECT *, long lArgument)
527         if (!pCurrentDlg) return;
529         switch (lArgument) {
531         case 0: // get directory
532                 pCurrentDlg->SetDirectory(fl_get_input(pFileDlgForm->DirBox));
533                 pCurrentDlg->Reread();
534                 break;
536         case 1: // get mask
537                 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
538                 pCurrentDlg->Reread();
539                 break;
541         case 2: // list
542                 pCurrentDlg->HandleListHit();
543                 break;  
545         case 10: // rescan
546                 pCurrentDlg->SetDirectory(fl_get_input(pFileDlgForm->DirBox));
547                 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
548                 pCurrentDlg->Reread();
549                 break;
551         case 11: // home
552                 pCurrentDlg->SetDirectory(GetEnvPath("HOME"));
553                 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
554                 pCurrentDlg->Reread();
555                 break;
557         case 12: // user button 1
558                 if (!pCurrentDlg->pszUserPath1.empty()) {
559                         pCurrentDlg->SetDirectory(pCurrentDlg->pszUserPath1);
560                         pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
561                         pCurrentDlg->Reread();
562                 }
563                 break;
565         case 13: // user button 2
566                 if (!pCurrentDlg->pszUserPath2.empty()) {
567                         pCurrentDlg->SetDirectory(pCurrentDlg->pszUserPath2);
568                         pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
569                         pCurrentDlg->Reread();
570                 }
571                 break;
573         }
576 extern "C" void C_LyXFileDlg_FileDlgCB(FL_OBJECT *ob, long data) 
578         LyXFileDlg::FileDlgCB(ob, data);
582 // Handle callback from list
583 void LyXFileDlg::HandleListHit()
585         // set info line
586         int iSelect = fl_get_browser(pFileDlgForm->List);
587         if (iSelect > iDepth)  {
588                 SetInfoLine(pCurrentNames[iSelect - iDepth - 1].pszLsEntry);
589         } else {
590                 SetInfoLine(string());
591         }
595 // Callback for double click in list
596 void LyXFileDlg::DoubleClickCB(FL_OBJECT *, long)
598         if (pCurrentDlg->HandleDoubleClick())
599                 // Simulate click on OK button
600                 pCurrentDlg->Force(false);
603 extern "C" void C_LyXFileDlg_DoubleClickCB(FL_OBJECT *ob, long data)
605         LyXFileDlg::DoubleClickCB(ob, data);
608 // Handle double click from list
609 bool LyXFileDlg::HandleDoubleClick()
611         bool isDir;
612         string pszTemp;
613         int iSelect;  
615         // set info line
616         isDir = true;
617         iSelect = fl_get_browser(pFileDlgForm->List);
618         if (iSelect > iDepth)  {
619                 pszTemp = pCurrentNames[iSelect - iDepth - 1].pszName;
620                 SetInfoLine(pCurrentNames[iSelect - iDepth - 1].pszLsEntry);
621                 if (!suffixIs(pszTemp, '/')) {
622                         isDir = false;
623                         fl_set_input(pFileDlgForm->Filename, pszTemp.c_str());
624                 }
625         } else if (iSelect !=0) {
626                 SetInfoLine(string());
627         } else
628                 return true;
630         // executes action
631         if (isDir) {
633                 int i;
634                 string Temp;
636                 // builds new directory name
637                 if (iSelect > iDepth) {
638                         // Directory deeper down
639                         // First, get directory with trailing /
640                         Temp = fl_get_input(pFileDlgForm->DirBox);
641                         if (!suffixIs(Temp, '/'))
642                                 Temp += '/';
643                         Temp += pszTemp;
644                 } else {
645                         // Directory higher up
646                         Temp.clear();
647                         for (i = 0; i < iSelect; ++i) {
648                                 string piece = fl_get_browser_line(pFileDlgForm->List, i+1);
649                                 // The '+2' is here to count the '@b' (JMarc)
650                                 Temp += piece.substr(i + 2);
651                         }
652                 }
654                 // assigns it
655                 SetDirectory(Temp);
656                 Reread();
657                 return false;
658         }
659         return true;
663 // Handle OK button call
664 bool LyXFileDlg::HandleOK()
666         string pszTemp;
668         // mask was changed
669         pszTemp = fl_get_input(pFileDlgForm->PatBox);
670         if (pszTemp!=pszMask) {
671                 SetMask(pszTemp);
672                 Reread();
673                 return false;
674         }
676         // directory was changed
677         pszTemp = fl_get_input(pFileDlgForm->DirBox);
678         if (pszTemp!=pszDirectory) {
679                 SetDirectory(pszTemp);
680                 Reread();
681                 return false;
682         }
683         
684         // Handle return from list
685         int select = fl_get_browser(pFileDlgForm->List);
686         if (select > iDepth) {
687                 string temp = pCurrentNames[select - iDepth - 1].pszName;
688                 if (!suffixIs(temp, '/')) {
689                         // If user didn't type anything, use browser
690                         string name = fl_get_input(pFileDlgForm->Filename);
691                         if (name.empty()) {
692                                 fl_set_input(pFileDlgForm->Filename, temp.c_str());
693                         }
694                         return true;
695                 }
696         }
697         
698         // Emulate a doubleclick
699         return HandleDoubleClick();
703 // Handle Cancel CB from WM close
704 int LyXFileDlg::CancelCB(FL_FORM *, void *)
706         // Simulate a click on the cancel button
707         pCurrentDlg->Force(true);
708         return FL_IGNORE;
711 extern "C" int C_LyXFileDlg_CancelCB(FL_FORM *fl, void *xev)
713         return LyXFileDlg::CancelCB(fl, xev);
716 // Simulates a click on OK/Cancel
717 void LyXFileDlg::Force(bool cancel)
719         if (cancel) {
720                 force_cancel = true;
721                 fl_set_button(pFileDlgForm->Cancel, 1);
722         } else {
723                 force_ok = true;
724                 fl_set_button(pFileDlgForm->Ready, 1);
725         }
726         // Start timer to break fl_do_forms loop soon
727         fl_set_timer(pFileDlgForm->timer, 0.1);
731 // Select: launches dialog and returns selected file
732 string LyXFileDlg::Select(string const & title, string const & path, 
733                            string const & mask, string const & suggested)
735         bool isOk;
737         // handles new mask and path
738         isOk = true;
739         if (!mask.empty()) {
740                 SetMask(mask);
741                 isOk = false;
742         }
743         if (!path.empty()) {
744                 SetDirectory(path);
745                 isOk = false;
746         }
747         if (!isOk) Reread();
748         else {
749                 fl_select_browser_line(pFileDlgForm->List, 1);
750                 fl_set_browser_topline(pFileDlgForm->List, 1);
751         }
753         // checks whether dialog can be started
754         if (pCurrentDlg) return string();
755         pCurrentDlg = this;
757         // runs dialog
758         SetInfoLine (string());
759         fl_set_input(pFileDlgForm->Filename, suggested.c_str());
760         fl_set_button(pFileDlgForm->Cancel, 0);
761         fl_set_button(pFileDlgForm->Ready, 0);
762         fl_set_focus_object(pFileDlgForm->FileDlg, pFileDlgForm->Filename);
763         fl_deactivate_all_forms();
764         fl_show_form(pFileDlgForm->FileDlg, FL_PLACE_MOUSE | FL_FREE_SIZE,
765                      FL_FULLBORDER, title.c_str());
767         isOk = RunDialog();
769         fl_hide_form(pFileDlgForm->FileDlg);
770         fl_activate_all_forms();
771         pCurrentDlg = 0;
773         // Returns filename or string() if no valid selection was made
774         if (!isOk || !fl_get_input(pFileDlgForm->Filename)[0]) return string();
776         pszFileName = fl_get_input(pFileDlgForm->Filename);
778         if (!AbsolutePath(pszFileName)) {
779                 pszFileName = AddName(fl_get_input(pFileDlgForm->DirBox), 
780                                       pszFileName);
781         }
782         return pszFileName;