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"
69 static const long SIX_MONTH_SEC = 6L * 30L * 24L * 60L * 60L; // six months, in seconds
70 static const long ONE_HOUR_SEC = 60L * 60L;
72 // *** User cache class implementation
74 // global instance (user cache root)
75 UserCache lyxUserCache = UserCache(string(), 0, 0);
77 // some "C" wrappers around callbacks
78 extern "C" void C_LyXFileDlg_FileDlgCB(FL_OBJECT *, long lArgument);
79 extern "C" void C_LyXFileDlg_DoubleClickCB(FL_OBJECT *, long);
80 extern "C" int C_LyXFileDlg_CancelCB(FL_FORM *, void *);
81 extern "C" int C_LyXDirEntryC_ldeCompProc(const void * r1,
84 // Add: creates a new user entry
85 UserCache * UserCache::Add(uid_t ID)
88 struct passwd * pEntry;
91 if ((pEntry = getpwuid(ID)))
92 pszNewName = pEntry->pw_name;
94 pszNewName = tostr(ID);
98 return new UserCache(pszNewName, ID, pRoot);
102 UserCache::UserCache(string const & pszName, uid_t ID, UserCache * pRoot)
107 pNext = pRoot->pNext;
115 this->pszName = pszName;
120 UserCache::~UserCache()
122 if (pNext) delete pNext;
126 // Find: seeks user name from user ID
127 string UserCache::Find(uid_t ID)
129 if ((!pszName.empty()) && (this->ID == ID)) return pszName;
130 if (pNext) return pNext->Find(ID);
132 return pRoot->Add(ID)->pszName;
136 // *** Group cache class implementation
138 // global instance (group cache root)
139 GroupCache lyxGroupCache = GroupCache(string(), 0, 0);
141 // Add: creates a new group entry
142 GroupCache * GroupCache::Add(gid_t ID)
145 struct group * pEntry;
148 if ((pEntry = getgrgid(ID))) pszNewName = pEntry->gr_name;
150 pszNewName = tostr(ID);
154 return new GroupCache(pszNewName, ID, pRoot);
158 GroupCache::GroupCache(string const & pszName, gid_t ID, GroupCache * pRoot)
163 pNext = pRoot->pNext;
171 this->pszName = pszName;
176 GroupCache::~GroupCache()
178 if (pNext) delete pNext;
182 // Find: seeks group name from group ID
183 string GroupCache::Find(gid_t ID)
185 if ((!pszName.empty()) && (this->ID == ID)) return pszName;
186 if (pNext) return pNext->Find(ID);
188 return pRoot->Add(ID)->pszName;
191 // *** LyXDirEntry internal structure implementation
193 // ldeCompProc: compares two LyXDirEntry objects content (used for qsort)
194 int LyXDirEntry::ldeCompProc(const LyXDirEntry * r1,
195 const LyXDirEntry * r2)
197 bool r1d = suffixIs(r1->pszName, '/');
198 bool r2d = suffixIs(r2->pszName, '/');
199 if (r1d && !r2d) return -1;
200 if (!r1d && r2d) return 1;
201 return r1->pszName.compare(r2->pszName);
204 extern "C" int C_LyXDirEntry_ldeCompProc(const void * r1,
207 return LyXDirEntry::ldeCompProc((const LyXDirEntry *)r1,
208 (const LyXDirEntry *)r2);
211 // *** LyXFileDlg class implementation
214 FD_FileDlg * LyXFileDlg::pFileDlgForm = 0;
215 LyXFileDlg * LyXFileDlg::pCurrentDlg = 0;
218 // Reread: updates dialog list to match class directory
219 void LyXFileDlg::Reread()
223 struct dirent * pDirEntry;
231 pDirectory = opendir(pszDirectory.c_str());
233 WriteFSAlert(_("Warning! Couldn't open directory."),
235 pszDirectory = GetCWD();
236 pDirectory = opendir(pszDirectory.c_str());
239 // Clear the present namelist
241 delete [] pCurrentNames;
246 fl_hide_object(pFileDlgForm->List);
247 fl_clear_browser(pFileDlgForm->List);
248 fl_set_input(pFileDlgForm->DirBox, pszDirectory.c_str());
250 // Splits complete directory name into directories and compute depth
255 File = split(File, Temp, '/');
257 while (!File.empty() || !Temp.empty()) {
258 string dline = "@b"+line + Temp + '/';
259 fl_add_browser_line(pFileDlgForm->List, dline.c_str());
260 File = split(File, Temp, '/');
265 // Allocate names array
267 rewinddir(pDirectory);
268 while ((readdir(pDirectory))) ++iNumNames;
269 pCurrentNames = new LyXDirEntry[iNumNames];
271 // Parses all entries of the given subdirectory
273 time_t curTime = time(0);
274 rewinddir(pDirectory);
275 while ((pDirEntry = readdir(pDirectory))) {
277 bool isLink = false, isDir = false;
279 // If the pattern doesn't start with a dot, skip hidden files
280 if (!pszMask.empty() && pszMask[0] != '.' &&
281 pDirEntry->d_name[0] == '.')
285 string fname = pDirEntry->d_name;
287 // Under all circumstances, "." and ".." are not wanted
288 if (fname == "." || fname == "..")
292 File = AddName(pszDirectory, fname);
294 fileInfo.newFile(File, true);
295 fileInfo.modeString(szMode);
296 unsigned int nlink = fileInfo.getNumberOfLinks();
297 string user = lyxUserCache.Find(fileInfo.getUid());
298 string group = lyxGroupCache.Find(fileInfo.getGid());
300 time_t modtime = fileInfo.getModificationTime();
301 Time = ctime(&modtime);
303 if (curTime > fileInfo.getModificationTime() + SIX_MONTH_SEC
304 || curTime < fileInfo.getModificationTime()
306 // The file is fairly old or in the future. POSIX says
307 // the cutoff is 6 months old. Allow a 1 hour slop
308 // factor for what is considered "the future", to
309 // allow for NFS server/client clock disagreement.
310 // Show the year instead of the time of day.
312 Time.erase(15, string::npos);
314 Time.erase(16, string::npos);
317 Buffer = string(szMode) + ' ' +
321 Time.substr(4, string::npos) + ' ';
323 Buffer += pDirEntry->d_name;
324 Buffer += fileInfo.typeIndicator();
326 if ((isLink = fileInfo.isLink())) {
329 if (LyXReadLink(File, Link)) {
333 // This gives the FileType of the file that
334 // is really pointed too after resolving all
335 // symlinks. This is not necessarily the same
336 // as the type of Link (which could again be a
337 // link). Is that intended?
339 fileInfo.newFile(File);
340 Buffer += fileInfo.typeIndicator();
344 // filters files according to pattern and type
345 if (fileInfo.isRegular()
347 || fileInfo.isBlock()
348 || fileInfo.isFifo()) {
349 if (!regexMatch(fname, pszMask))
351 } else if (!(isDir = fileInfo.isDir()))
354 // Note pszLsEntry is an string!
355 pCurrentNames[iNumNames].pszLsEntry = Buffer;
359 if (isDir) temp += '/';
360 pCurrentNames[iNumNames].pszName = temp;
362 // creates displayed name
363 temp = pDirEntry->d_name;
367 temp += fileInfo.typeIndicator();
369 pCurrentNames[iNumNames++].pszDisplayed = temp;
372 closedir(pDirectory);
375 qsort(pCurrentNames, iNumNames, sizeof(LyXDirEntry),
376 C_LyXDirEntry_ldeCompProc);
378 // Add them to directory box
379 for (i = 0; i < iNumNames; ++i) {
380 string temp = line + pCurrentNames[i].pszDisplayed;
381 fl_add_browser_line(pFileDlgForm->List, temp.c_str());
383 fl_set_browser_topline(pFileDlgForm->List, iDepth);
384 fl_show_object(pFileDlgForm->List);
389 // SetDirectory: sets dialog current directory
390 void LyXFileDlg::SetDirectory(string const & Path)
392 if (!pszDirectory.empty()) {
393 string TempPath = ExpandPath(Path); // Expand ~/
394 TempPath = MakeAbsPath(TempPath, pszDirectory);
395 pszDirectory = MakeAbsPath(TempPath);
396 } else pszDirectory = MakeAbsPath(Path);
400 // SetMask: sets dialog file mask
401 void LyXFileDlg::SetMask(string const & NewMask)
404 fl_set_input(pFileDlgForm->PatBox, pszMask.c_str());
408 // SetInfoLine: sets dialog information line
409 void LyXFileDlg::SetInfoLine(string const & Line)
412 fl_set_object_label(pFileDlgForm->FileInfo, pszInfoLine.c_str());
416 LyXFileDlg::LyXFileDlg()
419 pszDirectory = MakeAbsPath(string("."));
422 // Creates form if necessary.
424 pFileDlgForm = create_form_FileDlg();
425 // Set callbacks. This means that we don't need a patch file
426 fl_set_object_callback(pFileDlgForm->DirBox,
427 C_LyXFileDlg_FileDlgCB, 0);
428 fl_set_object_callback(pFileDlgForm->PatBox,
429 C_LyXFileDlg_FileDlgCB, 1);
430 fl_set_object_callback(pFileDlgForm->List,
431 C_LyXFileDlg_FileDlgCB, 2);
432 fl_set_object_callback(pFileDlgForm->Filename,
433 C_LyXFileDlg_FileDlgCB, 3);
434 fl_set_object_callback(pFileDlgForm->Rescan,
435 C_LyXFileDlg_FileDlgCB, 10);
436 fl_set_object_callback(pFileDlgForm->Home,
437 C_LyXFileDlg_FileDlgCB, 11);
438 fl_set_object_callback(pFileDlgForm->User1,
439 C_LyXFileDlg_FileDlgCB, 12);
440 fl_set_object_callback(pFileDlgForm->User2,
441 C_LyXFileDlg_FileDlgCB, 13);
443 // Make sure pressing the close box doesn't crash LyX. (RvdK)
444 fl_set_form_atclose(pFileDlgForm->FileDlg,
445 C_LyXFileDlg_CancelCB, 0);
446 // Register doubleclick callback
447 fl_set_browser_dblclick_callback(pFileDlgForm->List,
448 C_LyXFileDlg_DoubleClickCB, 0);
450 fl_hide_object(pFileDlgForm->User1);
451 fl_hide_object(pFileDlgForm->User2);
455 LyXFileDlg::~LyXFileDlg()
457 // frees directory entries
459 delete [] pCurrentNames;
464 // SetButton: sets file selector user button action
465 void LyXFileDlg::SetButton(int iIndex, string const & pszName,
466 string const & pszPath)
472 pObject = pFileDlgForm->User1;
473 pTemp = &pszUserPath1;
474 } else if (iIndex == 1) {
475 pObject = pFileDlgForm->User2;
476 pTemp = &pszUserPath2;
479 if (!pszName.empty() && !pszPath.empty()) {
480 fl_set_object_label(pObject, pszName.c_str());
481 fl_show_object(pObject);
484 fl_hide_object(pObject);
490 // GetDirectory: gets last dialog directory
491 string LyXFileDlg::GetDirectory()
493 if (!pszDirectory.empty())
500 // RunDialog: handle dialog during file selection
501 bool LyXFileDlg::RunDialog()
503 force_cancel = false;
509 FL_OBJECT * pObject = fl_do_forms();
511 if (pObject == pFileDlgForm->Ready) {
514 } else if (pObject == pFileDlgForm->Cancel
523 // XForms objects callback (static)
524 void LyXFileDlg::FileDlgCB(FL_OBJECT *, long lArgument)
526 if (!pCurrentDlg) return;
530 case 0: // get directory
531 pCurrentDlg->SetDirectory(fl_get_input(pFileDlgForm->DirBox));
532 pCurrentDlg->Reread();
536 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
537 pCurrentDlg->Reread();
541 pCurrentDlg->HandleListHit();
545 pCurrentDlg->SetDirectory(fl_get_input(pFileDlgForm->DirBox));
546 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
547 pCurrentDlg->Reread();
551 pCurrentDlg->SetDirectory(GetEnvPath("HOME"));
552 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
553 pCurrentDlg->Reread();
556 case 12: // user button 1
557 if (!pCurrentDlg->pszUserPath1.empty()) {
558 pCurrentDlg->SetDirectory(pCurrentDlg->pszUserPath1);
559 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
560 pCurrentDlg->Reread();
564 case 13: // user button 2
565 if (!pCurrentDlg->pszUserPath2.empty()) {
566 pCurrentDlg->SetDirectory(pCurrentDlg->pszUserPath2);
567 pCurrentDlg->SetMask(fl_get_input(pFileDlgForm->PatBox));
568 pCurrentDlg->Reread();
575 extern "C" void C_LyXFileDlg_FileDlgCB(FL_OBJECT *ob, long data)
577 LyXFileDlg::FileDlgCB(ob, data);
581 // Handle callback from list
582 void LyXFileDlg::HandleListHit()
585 int iSelect = fl_get_browser(pFileDlgForm->List);
586 if (iSelect > iDepth) {
587 SetInfoLine(pCurrentNames[iSelect - iDepth - 1].pszLsEntry);
589 SetInfoLine(string());
594 // Callback for double click in list
595 void LyXFileDlg::DoubleClickCB(FL_OBJECT *, long)
597 if (pCurrentDlg->HandleDoubleClick())
598 // Simulate click on OK button
599 pCurrentDlg->Force(false);
602 extern "C" void C_LyXFileDlg_DoubleClickCB(FL_OBJECT *ob, long data)
604 LyXFileDlg::DoubleClickCB(ob, data);
607 // Handle double click from list
608 bool LyXFileDlg::HandleDoubleClick()
616 iSelect = fl_get_browser(pFileDlgForm->List);
617 if (iSelect > iDepth) {
618 pszTemp = pCurrentNames[iSelect - iDepth - 1].pszName;
619 SetInfoLine(pCurrentNames[iSelect - iDepth - 1].pszLsEntry);
620 if (!suffixIs(pszTemp, '/')) {
622 fl_set_input(pFileDlgForm->Filename, pszTemp.c_str());
624 } else if (iSelect != 0) {
625 SetInfoLine(string());
635 // builds new directory name
636 if (iSelect > iDepth) {
637 // Directory deeper down
638 // First, get directory with trailing /
639 Temp = fl_get_input(pFileDlgForm->DirBox);
640 if (!suffixIs(Temp, '/'))
644 // Directory higher up
646 for (i = 0; i < iSelect; ++i) {
647 string piece = fl_get_browser_line(pFileDlgForm->List, i+1);
648 // The '+2' is here to count the '@b' (JMarc)
649 Temp += piece.substr(i + 2);
662 // Handle OK button call
663 bool LyXFileDlg::HandleOK()
668 pszTemp = fl_get_input(pFileDlgForm->PatBox);
669 if (pszTemp!= pszMask) {
675 // directory was changed
676 pszTemp = fl_get_input(pFileDlgForm->DirBox);
677 if (pszTemp!= pszDirectory) {
678 SetDirectory(pszTemp);
683 // Handle return from list
684 int select = fl_get_browser(pFileDlgForm->List);
685 if (select > iDepth) {
686 string temp = pCurrentNames[select - iDepth - 1].pszName;
687 if (!suffixIs(temp, '/')) {
688 // If user didn't type anything, use browser
689 string name = fl_get_input(pFileDlgForm->Filename);
691 fl_set_input(pFileDlgForm->Filename, temp.c_str());
697 // Emulate a doubleclick
698 return HandleDoubleClick();
702 // Handle Cancel CB from WM close
703 int LyXFileDlg::CancelCB(FL_FORM *, void *)
705 // Simulate a click on the cancel button
706 pCurrentDlg->Force(true);
710 extern "C" int C_LyXFileDlg_CancelCB(FL_FORM *fl, void *xev)
712 return LyXFileDlg::CancelCB(fl, xev);
715 // Simulates a click on OK/Cancel
716 void LyXFileDlg::Force(bool cancel)
720 fl_set_button(pFileDlgForm->Cancel, 1);
723 fl_set_button(pFileDlgForm->Ready, 1);
725 // Start timer to break fl_do_forms loop soon
726 fl_set_timer(pFileDlgForm->timer, 0.1);
730 // Select: launches dialog and returns selected file
731 string LyXFileDlg::Select(string const & title, string const & path,
732 string const & mask, string const & suggested)
736 // handles new mask and path
748 fl_select_browser_line(pFileDlgForm->List, 1);
749 fl_set_browser_topline(pFileDlgForm->List, 1);
752 // checks whether dialog can be started
753 if (pCurrentDlg) return string();
757 SetInfoLine (string());
758 fl_set_input(pFileDlgForm->Filename, suggested.c_str());
759 fl_set_button(pFileDlgForm->Cancel, 0);
760 fl_set_button(pFileDlgForm->Ready, 0);
761 fl_set_focus_object(pFileDlgForm->FileDlg, pFileDlgForm->Filename);
762 fl_deactivate_all_forms();
763 fl_show_form(pFileDlgForm->FileDlg, FL_PLACE_MOUSE | FL_FREE_SIZE,
764 FL_FULLBORDER, title.c_str());
768 fl_hide_form(pFileDlgForm->FileDlg);
769 fl_activate_all_forms();
772 // Returns filename or string() if no valid selection was made
773 if (!isOk || !fl_get_input(pFileDlgForm->Filename)[0]) return string();
775 pszFileName = fl_get_input(pFileDlgForm->Filename);
777 if (!AbsolutePath(pszFileName)) {
778 pszFileName = AddName(fl_get_input(pFileDlgForm->DirBox),