2 /* This file is part of
3 * ======================================================
5 * LyX, The Document Processor
7 * Copyright 1995 Matthias Ettrich
8 * Copyright 1995-1999 The LyX Team.
10 * ======================================================*/
21 #include "lyx_gui_misc.h" // CancelCloseCB
22 #include "support/FileInfo.h"
31 # define NAMLEN(dirent) strlen((dirent)->d_name)
33 # define dirent direct
34 # define NAMLEN(dirent) (dirent)->d_namlen
36 # include <sys/ndir.h>
46 #if TIME_WITH_SYS_TIME
47 # include <sys/time.h>
51 # include <sys/time.h>
58 extern "C" int gettimeofday(struct timeval *,struct timezone *);
59 #define remove(a) unlink(a)
63 #pragma implementation
66 #include "support/filetools.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,
85 // Add: creates a new user entry
86 UserCache * UserCache::Add(uid_t ID)
89 struct passwd * pEntry;
92 if ((pEntry = getpwuid(ID)))
93 pszNewName = pEntry->pw_name;
95 pszNewName = tostr(ID);
99 return new UserCache(pszNewName, ID, pRoot);
103 UserCache::UserCache(string const & pszName, uid_t ID, UserCache * pRoot)
108 pNext = pRoot->pNext;
116 this->pszName = pszName;
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)
146 struct group * pEntry;
149 if ((pEntry = getgrgid(ID))) pszNewName = pEntry->gr_name;
151 pszNewName = tostr(ID);
155 return new GroupCache(pszNewName, ID, pRoot);
159 GroupCache::GroupCache(string const & pszName, gid_t ID, GroupCache * pRoot)
164 pNext = pRoot->pNext;
172 this->pszName = pszName;
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,
208 return LyXDirEntry::ldeCompProc((const LyXDirEntry *)r1,
209 (const LyXDirEntry *)r2);
212 // *** LyXFileDlg class implementation
215 FD_FileDlg * LyXFileDlg::pFileDlgForm = 0;
216 LyXFileDlg * LyXFileDlg::pCurrentDlg = 0;
219 // Reread: updates dialog list to match class directory
220 void LyXFileDlg::Reread()
224 struct dirent * pDirEntry;
232 pDirectory = opendir(pszDirectory.c_str());
234 WriteFSAlert(_("Warning! Couldn't open directory."),
236 pszDirectory = GetCWD();
237 pDirectory = opendir(pszDirectory.c_str());
240 // Clear the present namelist
242 delete [] pCurrentNames;
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
256 File = split(File, Temp, '/');
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, '/');
266 // Allocate names array
268 rewinddir(pDirectory);
269 while ((readdir(pDirectory))) ++iNumNames;
270 pCurrentNames = new LyXDirEntry[iNumNames];
272 // Parses all entries of the given subdirectory
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] == '.')
286 string fname = pDirEntry->d_name;
288 // Under all circumstances, "." and ".." are not wanted
289 if (fname == "." || fname == "..")
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);
304 if (curTime > fileInfo.getModificationTime() + SIX_MONTH_SEC
305 || curTime < fileInfo.getModificationTime()
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.
313 Time.erase(15, string::npos);
315 Time.erase(16, string::npos);
318 Buffer = string(szMode) + ' ' +
322 Time.substr(4, string::npos) + ' ';
324 Buffer += pDirEntry->d_name;
325 Buffer += fileInfo.typeIndicator();
327 if ((isLink = fileInfo.isLink())) {
330 if (LyXReadLink(File,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?
340 fileInfo.newFile(File);
341 Buffer += fileInfo.typeIndicator();
345 // filters files according to pattern and type
346 if (fileInfo.isRegular()
348 || fileInfo.isBlock()
349 || fileInfo.isFifo()) {
350 if (!regexMatch(fname, pszMask))
352 } else if (!(isDir = fileInfo.isDir()))
355 // Note pszLsEntry is an string!
356 pCurrentNames[iNumNames].pszLsEntry = Buffer;
360 if (isDir) temp += '/';
361 pCurrentNames[iNumNames].pszName = temp;
363 // creates displayed name
364 temp = pDirEntry->d_name;
368 temp += fileInfo.typeIndicator();
370 pCurrentNames[iNumNames++].pszDisplayed = temp;
373 closedir(pDirectory);
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());
384 fl_set_browser_topline(pFileDlgForm->List,iDepth);
385 fl_show_object(pFileDlgForm->List);
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)
405 fl_set_input(pFileDlgForm->PatBox, pszMask.c_str());
409 // SetInfoLine: sets dialog information line
410 void LyXFileDlg::SetInfoLine(string const & Line)
413 fl_set_object_label(pFileDlgForm->FileInfo, pszInfoLine.c_str());
417 LyXFileDlg::LyXFileDlg()
420 pszDirectory = MakeAbsPath(string("."));
423 // Creates form if necessary.
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);
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);
451 fl_hide_object(pFileDlgForm->User1);
452 fl_hide_object(pFileDlgForm->User2);
456 LyXFileDlg::~LyXFileDlg()
458 // frees directory entries
460 delete [] pCurrentNames;
465 // SetButton: sets file selector user button action
466 void LyXFileDlg::SetButton(int iIndex, string const & pszName,
467 string const & pszPath)
473 pObject = pFileDlgForm->User1;
474 pTemp = &pszUserPath1;
475 } else if (iIndex == 1) {
476 pObject = pFileDlgForm->User2;
477 pTemp = &pszUserPath2;
480 if (!pszName.empty() && !pszPath.empty()) {
481 fl_set_object_label(pObject, pszName.c_str());
482 fl_show_object(pObject);
485 fl_hide_object(pObject);
491 // GetDirectory: gets last dialog directory
492 string LyXFileDlg::GetDirectory()
494 if (!pszDirectory.empty())
501 // RunDialog: handle dialog during file selection
502 bool LyXFileDlg::RunDialog()
504 force_cancel = false;
510 FL_OBJECT * pObject = fl_do_forms();
512 if (pObject == pFileDlgForm->Ready) {
515 } else if (pObject == pFileDlgForm->Cancel
524 // XForms objects callback (static)
525 void LyXFileDlg::FileDlgCB(FL_OBJECT *, long lArgument)
527 if (!pCurrentDlg) return;
531 case 0: // get directory
532 pCurrentDlg->SetDirectory(fl_get_input(pFileDlgForm->DirBox));
533 pCurrentDlg->Reread();
537 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
538 pCurrentDlg->Reread();
542 pCurrentDlg->HandleListHit();
546 pCurrentDlg->SetDirectory(fl_get_input(pFileDlgForm->DirBox));
547 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
548 pCurrentDlg->Reread();
552 pCurrentDlg->SetDirectory(GetEnvPath("HOME"));
553 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
554 pCurrentDlg->Reread();
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();
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();
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()
586 int iSelect = fl_get_browser(pFileDlgForm->List);
587 if (iSelect > iDepth) {
588 SetInfoLine(pCurrentNames[iSelect - iDepth - 1].pszLsEntry);
590 SetInfoLine(string());
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()
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, '/')) {
623 fl_set_input(pFileDlgForm->Filename, pszTemp.c_str());
625 } else if (iSelect !=0) {
626 SetInfoLine(string());
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, '/'))
645 // Directory higher up
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);
663 // Handle OK button call
664 bool LyXFileDlg::HandleOK()
669 pszTemp = fl_get_input(pFileDlgForm->PatBox);
670 if (pszTemp!=pszMask) {
676 // directory was changed
677 pszTemp = fl_get_input(pFileDlgForm->DirBox);
678 if (pszTemp!=pszDirectory) {
679 SetDirectory(pszTemp);
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);
692 fl_set_input(pFileDlgForm->Filename, temp.c_str());
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);
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)
721 fl_set_button(pFileDlgForm->Cancel, 1);
724 fl_set_button(pFileDlgForm->Ready, 1);
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)
737 // handles new mask and path
749 fl_select_browser_line(pFileDlgForm->List, 1);
750 fl_set_browser_topline(pFileDlgForm->List, 1);
753 // checks whether dialog can be started
754 if (pCurrentDlg) return string();
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());
769 fl_hide_form(pFileDlgForm->FileDlg);
770 fl_activate_all_forms();
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),