This commit was manufactured by cvs2svn to create tag
[lyx.git] / src / bufferlist.C
blob1a5afbd6c6e538ca53b1394eb035fd619e1ea7b9
1 /* This file is part of
2  * ======================================================
3  * 
4  *           LyX, The Document Word Processor
5  *
6  *          Copyright 1995 Matthias Ettrich
7  *          Copyright 1995-1999 The LyX Team. 
8  *
9  *           This file is Copyright 1996-1999
10  *           Lars Gullik Bjønnes
11  *
12  * ======================================================
13  */
15 #ifdef __GNUG__
16 #pragma implementation
17 #endif
19 #include <config.h>
20 #include <sys/types.h>
21 #include <utime.h>
22 #include "bufferlist.h"
23 #include "lyx_main.h"
24 #include "minibuffer.h"
25 #include "support/FileInfo.h"
26 #include "support/filetools.h"
27 #include "lyx_gui_misc.h"
28 #include "lastfiles.h"
29 #include "debug.h"
30 #include "lyxrc.h"
31 #include "lyxscreen.h"
32 #include "lyxtext.h"
33 #include "lyx_cb.h"
34 #include "gettext.h"
36 extern BufferView *current_view;
37 extern MiniBuffer *minibuffer;
38 extern void SmallUpdate(signed char);
39 extern void BeforeChange();
40 extern int RunLinuxDoc(int, string const &);
43 // Class BufferStorage
46 BufferStorage::BufferStorage()
48         // Initialize the buffer array
49         for (int i=NUMBER_OF_BUFFERS-1; i >=0; i--) {
50                 buffer[i] = 0;
51         }       
55 bool BufferStorage::isEmpty()
57         for (int i=NUMBER_OF_BUFFERS-1; i >=0; i--) {
58                 if (buffer[i]) return false;
59         }
60         return true;
64 void BufferStorage::release(Buffer* buf)
66         int i=0;
67         for (i=0; i<NUMBER_OF_BUFFERS; i++)
68                 if (buffer[i] == buf) break;
69         Buffer *tmpbuf = buffer[i];
70         buffer[i] = 0;
71         delete tmpbuf;
75 Buffer* BufferStorage::newBuffer(string const &s,
76                                  LyXRC *lyxrc,
77                                  bool ronly)
79         int i=0;
80         while (i < NUMBER_OF_BUFFERS - 1
81                && buffer[i]) i++;
82         buffer[i] = new Buffer(s, lyxrc, ronly);
83         buffer[i]->params.useClassDefaults();
84         lyxerr.debug() << "Assigning to buffer " << i << endl;
85         return buffer[i];
90 // Class BufferStrorage_Iter
93 Buffer* BufferStorage_Iter::operator() ()
95         int i=0;
96         for (i=index; i < BufferStorage::NUMBER_OF_BUFFERS; i++) {
97                 if (cs->buffer[i]) {
98                         index = i+1;
99                         return cs->buffer[i];
100                 }
101         }
102         return 0;       
106 Buffer* BufferStorage_Iter::operator[] (int a)
108         // a is >=1
109         if (a<=0) return 0;
110         
111         int i=0;
112         while (a--) {
113                 while(!cs->buffer[i++]);
114         }
115         if (i-1 < BufferStorage::NUMBER_OF_BUFFERS)
116                 return cs->buffer[i-1];
117         return 0;       
122 // Class BufferList
124 BufferList::BufferList()
126         _state = BufferList::OK;
130 BufferList::~BufferList()
132         // I am sure something should be done here too.
136 bool BufferList::isEmpty()
138         return bstore.isEmpty();
141 extern void MenuWrite(Buffer*);
143 bool BufferList::QwriteAll()
145         bool askMoreConfirmation = false;
146         string unsaved;
147         BufferStorage_Iter biter(bstore);
148         Buffer *b=0;
149         while ((b=biter())) {
150                 if (!b->isLyxClean()) {
151                         switch(AskConfirmation(_("Changes in document:"),
152                                                MakeDisplayPath(b->filename,50),
153                                                _("Save document?"))) {
154                         case 1: // Yes
155                                 MenuWrite(b);
156                                 break;
157                         case 2: // No
158                                 askMoreConfirmation = true;
159                                 unsaved += MakeDisplayPath(b->filename,50);
160                                 unsaved += "\n";
161                                 break;
162                         case 3: // Cancel
163                                 return false;
164                         }
165                 }
166         }
167         if (askMoreConfirmation &&
168             lyxrc->exit_confirmation &&
169             !AskQuestion(_("Some documents were not saved:"),
170                          unsaved, _("Exit anyway?"))) {
171                 return false;
172         }
174         return true;
178 // Should probably be moved to somewhere else: BufferView? LyXView?
179 bool BufferList::write(Buffer *buf, bool makeBackup)
181         minibuffer->Set(_("Saving document"),
182                         MakeDisplayPath(buf->filename),"...");
184         // We don't need autosaves in the immediate future. (Asger)
185         buf->resetAutosaveTimers();
187         // make a backup
188         if (makeBackup) {
189                 string s = buf->filename + '~';
190                 // Rename is the wrong way of making a backup,
191                 // this is the correct way.
192                 /* truss cp fil fil2:
193                    lstat("LyXVC3.lyx", 0xEFFFF898)                 Err#2 ENOENT
194                    stat("LyXVC.lyx", 0xEFFFF688)                   = 0
195                    open("LyXVC.lyx", O_RDONLY)                     = 3
196                    open("LyXVC3.lyx", O_WRONLY|O_CREAT|O_TRUNC, 0600) = 4
197                    fstat(4, 0xEFFFF508)                            = 0
198                    fstat(3, 0xEFFFF508)                            = 0
199                    read(3, " # T h i s   f i l e   w".., 8192)     = 5579
200                    write(4, " # T h i s   f i l e   w".., 5579)    = 5579
201                    read(3, 0xEFFFD4A0, 8192)                       = 0
202                    close(4)                                        = 0
203                    close(3)                                        = 0
204                    chmod("LyXVC3.lyx", 0100644)                    = 0
205                    lseek(0, 0, SEEK_CUR)                           = 46440
206                    _exit(0)
207                  */
209                 // Should proabaly have some more error checking here.
210                 // Should be cleaned up in 0.13, at least a bit.
211                 // Doing it this way, also makes the inodes stay the same.
212                 // This is still not a very good solution, in particular we
213                 // might loose the owner of the backup.
214                 FileInfo finfo(buf->filename);
215                 if (finfo.exist()) {
216                         mode_t fmode = finfo.getMode();
218                         struct utimbuf *times =
219                                 (struct utimbuf*)new char[sizeof(struct utimbuf)];
220                         times->actime = finfo.getAccessTime();
221                         times->modtime = finfo.getModificationTime();
222                         long blksize = finfo.getBlockSize();
223                         lyxerr.debug() << "BlockSize: " << blksize << endl;
224                         FilePtr fin(buf->filename,FilePtr::read);
225                         FilePtr fout(s,FilePtr::truncate);
226                         if (fin() && fout()) {
227                                 char * cbuf = new char[blksize+1];
228                                 size_t c_read = 0;
229                                 size_t c_write = 0;
230                                 do {
231                                         c_read = fread(cbuf, 1, blksize, fin);
232                                         if (c_read != 0)
233                                                 c_write =
234                                                         fwrite(cbuf, 1, c_read, fout);
235                                 } while (c_read);
236                                 fin.close();
237                                 fout.close();
238                                 chmod(s.c_str(), fmode);
239                                 
240                                 if (utime(s.c_str(), times)) {
241                                         lyxerr << "utime error." << endl;
242                                 }
243                                 delete [] cbuf;
244                         } else {
245                                 lyxerr << "LyX was not able to make backupcopy. Beware." << endl;
246                         }
247                         delete[] times;
248                 }
249         }
250         
251         if (buf->writeFile(buf->filename,false)) {
252                 buf->markLyxClean();
254                 minibuffer->Set(_("Document saved as"),
255                                 MakeDisplayPath(buf->filename));
257                 // now delete the autosavefile
258                 string a = OnlyPath(buf->filename);
259                 a += '#';
260                 a += OnlyFilename(buf->filename);
261                 a += '#';
262                 FileInfo fileinfo(a);
263                 if (fileinfo.exist()) {
264                         if (remove(a.c_str()) != 0) {
265                                 WriteFSAlert(_("Could not delete "
266                                                "auto-save file!"), a);
267                         }
268                 }
269         } else {
270                 // Saving failed, so backup is not backup
271                 if (makeBackup) {
272                         string s = buf->filename + '~';
273                         rename(s.c_str(), buf->filename.c_str());
274                 }
275                 minibuffer->Set(_("Save failed!"));
276                 return false;
277         }
279         return true;
283 void BufferList::closeAll()
285         _state = BufferList::CLOSING;
286         
287         BufferStorage_Iter biter(bstore);
288         Buffer *b=0;
289         while ((b=biter())) {
290                 close(b);
291         }
292         _state = BufferList::OK;
296 void BufferList::resize()
298         BufferStorage_Iter biter(bstore);
299         Buffer *b=0;
300         while ((b=biter())) {
301                 b->resize();
302         }
306 bool BufferList::close(Buffer *buf)
308         buf->InsetUnlock();
309         
310         if (buf->paragraph && !buf->isLyxClean() && !quitting) {
311                 ProhibitInput();
312                 switch(AskConfirmation(_("Changes in document:"),
313                               MakeDisplayPath(buf->filename,50),
314                                       _("Save document?"))){
315                 case 1: // Yes
316                         if (write(buf)) {
317                                 lastfiles->newFile(buf->filename);
318                         } else {
319                                 AllowInput();
320                                 return false;
321                         }
322                         break;
323                 case 3: // Cancel
324                         AllowInput();
325                         return false;
326                 }
327                 AllowInput();
328         }
330         bstore.release(buf);
331         return true;
335 void BufferList::makePup(int pup)
336         /* This should be changed to return a char const *const
337            in the same way as for lastfiles.[hC]
338            */
340         int ant=0;
341         BufferStorage_Iter biter(bstore);
342         Buffer *b=0;
343         while ((b=biter())) {
344                 string relbuf = MakeDisplayPath(b->filename,30);
345                 fl_addtopup(pup, relbuf.c_str());
346                 ant++;
347         }
348         if (ant == 0) fl_addtopup(pup,_("No Documents Open!%t"));
352 Buffer* BufferList::first()
354         BufferStorage_Iter biter(bstore);
355         return biter();
359 Buffer* BufferList::getBuffer(int choice)
361         BufferStorage_Iter biter(bstore);
362         Buffer *b=0;
363         b = biter[choice];
364             
365         // Be careful, this could be 0.    
366         return b;
370 void BufferList::updateInset(Inset *inset, bool mark_dirty)
372         BufferStorage_Iter biter(bstore);
373         Buffer *b=0;
374         while ((b=biter())) {
375                 if (b->text && b->text->UpdateInset(inset)) {
376                         if (mark_dirty)
377                                 b->markDirty();
378                         break;
379                 }
380         }
384 int BufferList::unlockInset(UpdatableInset *inset)
386         if (!inset) return 1;
387         
388         BufferStorage_Iter biter(bstore);
389         Buffer *b=0;
390         while ((b=biter())) {
391                 if (b->the_locking_inset == inset) {
392                         b->InsetUnlock();
393                         return 0;
394                 }
395         }
396         return 1;
400 void BufferList::updateIncludedTeXfiles(string const & mastertmpdir)
402         BufferStorage_Iter biter(bstore);
403         Buffer *b=0;
404         while ((b=biter())) {
405                 if (!b->isDepClean(mastertmpdir)) {
406                         string writefile = mastertmpdir;
407                         writefile += '/';
408                         writefile += ChangeExtension(b->getFileName(), ".tex", true);
409                         b->makeLaTeXFile(writefile,mastertmpdir,false,true);
410                         b->markDepClean(mastertmpdir);
411                 }
412         }
416 void BufferList::emergencyWriteAll()
418         BufferStorage_Iter biter(bstore);
419         Buffer *b=0;
420         while ((b=biter())) {
421                 if (!b->isLyxClean()) {
422                         bool madeit=false;
423                         
424                         lyxerr <<_("lyx: Attempting to save"
425                                       " document ")
426                                << b->filename
427                                << _(" as...") << endl;
428                         
429                         for (int i=0; i<3 && !madeit; i++) {
430                                 string s;
431                                 
432                                 // We try to save three places:
433                                 // 1) Same place as document.
434                                 // 2) In HOME directory.
435                                 // 3) In "/tmp" directory.
436                                 if (i==0) {
437                                         s = b->filename;
438                                 } else if (i==1) {
439                                         s = AddName(GetEnvPath("HOME"),
440                                                     b->filename);
441                                 } else { // MakeAbsPath to prepend the current drive letter on OS/2
442                                         s = AddName(MakeAbsPath("/tmp/"),
443                                                     b->filename);
444                                 }
445                                 s += ".emergency";
446                                 
447                                 lyxerr << "  " << i+1 << ") " << s << endl;
448                                 
449                                 if (b->writeFile(s,true)) {
450                                         b->markLyxClean();
451                                         lyxerr << _("  Save seems successful. Phew.") << endl;
452                                         madeit = true;
453                                 } else if (i != 2) {
454                                         lyxerr << _("  Save failed! Trying...")
455                                                << endl;
456                                 } else {
457                                         lyxerr << _("  Save failed! Bummer. Document is lost.") << endl;
458                                 }
459                         }
460                 }
461         }
465 Buffer* BufferList::readFile(string const & s, bool ronly)
467         Buffer *b = bstore.newBuffer(s, lyxrc, ronly);
469         string ts = s;
470         string e = OnlyPath(s);
471         string a = e;
472         // File information about normal file
473         FileInfo fileInfo2(s);
475         // Check if emergency save file exists and is newer.
476         e += OnlyFilename(s) + ".emergency";
477         FileInfo fileInfoE(e);
479         bool use_emergency = false;
481         if (fileInfoE.exist() && fileInfo2.exist()) {
482                 if (fileInfoE.getModificationTime()
483                     > fileInfo2.getModificationTime()) {
484                         if (AskQuestion(_("An emergency save of this document exists!"),
485                                         MakeDisplayPath(s,50),
486                                         _("Try to load that instead?"))) {
487                                 ts = e;
488                                 // the file is not saved if we load the
489                                 // emergency file.
490                                 b->markDirty();
491                                 use_emergency = true;
492                         } else {
493                                 // Here, we should delete the emergency save
494                                 unlink(e.c_str());
495                         }
496                 }
497         }
499         if (!use_emergency) {
500                 // Now check if autosave file is newer.
501                 a += '#';
502                 a += OnlyFilename(s);
503                 a += '#';
504                 FileInfo fileInfoA(a);
505                 if (fileInfoA.exist() && fileInfo2.exist()) {
506                         if (fileInfoA.getModificationTime()
507                             > fileInfo2.getModificationTime()) {
508                                 if (AskQuestion(_("Autosave file is newer."),
509                                                 MakeDisplayPath(s,50),
510                                                 _("Load that one instead?"))) {
511                                         ts = a;
512                                         // the file is not saved if we load the
513                                         // autosave file.
514                                         b->markDirty();
515                                 } else {
516                                         // Here, we should delete the autosave
517                                         unlink(a.c_str());
518                                 }
519                         }
520                 }
521         }
522         // not sure if this is the correct place to begin LyXLex
523         LyXLex lex(0, 0);
524         lex.setFile(ts);
525         if (b->readFile(lex))
526                 return b;
527         else {
528                 bstore.release(b);
529                 return 0;
530         }
534 bool BufferList::exists(string const & s)
536         BufferStorage_Iter biter(bstore);
537         Buffer *b=0;
538         while ((b=biter())) {
539                 if (b->filename == s)
540                         return true;
541         }
542         return false;
546 Buffer* BufferList::getBuffer(string const &s)
548         BufferStorage_Iter biter(bstore);
549         Buffer *b=0;
550         while ((b=biter())) {
551                 if (b->filename ==s)
552                         return b;
553         }
554         return 0;
558 Buffer* BufferList::newFile(string const & name, string tname)
560         /* get a free buffer */ 
561         Buffer *b = bstore.newBuffer(name, lyxrc);
563         // use defaults.lyx as a default template if it exists.
564         if (tname.empty()) {
565                 tname = LibFileSearch("templates", "defaults.lyx");
566         }
567         if (!tname.empty() && IsLyXFilename(tname)){
568                 bool templateok = false;
569                 LyXLex lex(0,0);
570                 lex.setFile(tname);
571                 if (lex.IsOK()) {
572                         if (b->readFile(lex)) {
573                                 templateok = true;
574                         }
575                 }
576                 if (!templateok) {
577                         WriteAlert(_("Error!"),_("Unable to open template"), 
578                                    MakeDisplayPath(tname));
579                         // no template, start with empty buffer
580                         b->paragraph = new LyXParagraph();
581                 }
582         }
583         else {  // start with empty buffer
584                 b->paragraph = new LyXParagraph();
585         }
587         b->markDirty();
588         b->setReadonly(false);
589         
590         return b;
594 Buffer* BufferList::loadLyXFile(string const & filename, bool tolastfiles)
596         // make sure our path is absolute
597         string s = MakeAbsPath(filename);
599         // Is this done too early?
600         // Is it LinuxDoc?
601         if (IsSGMLFilename(s)) {
602                 FileInfo fi(s);
603                 if (fi.exist() && fi.readable()) {
604                         if (!RunLinuxDoc(-1, s)) {
605                                 s = ChangeExtension (s, ".lyx", false);
606                         } else { // sgml2lyx failed
607                                 WriteAlert(_("Error!"),
608                                            _("Could not convert file"),s);
609                                 return 0;
610                         }
611                 } else {
612                         // just change the extension and it will be
613                         // handled like a regular lyx file that does
614                         // not exist.
615                         s = ChangeExtension(s, ".lyx", false);
616                 }
617         }
618         
619         // file already open?
620         if (exists(s)) {
621                 if (AskQuestion(_("Document is already open:"), 
622                                 MakeDisplayPath(s,50),
623                                 _("Do you want to reload that document?"))) {
624                         // Reload is accomplished by closing and then loading
625                         if (!close(getBuffer(s))) {
626                                 return 0;
627                         }
628                         // Fall through to new load. (Asger)
629                 } else {
630                         // Here, we pretend that we just loaded the 
631                         // open document
632                         return getBuffer(s);
633                 }
634         }
635         Buffer *b=0;
636         bool ro = false;
637         switch (IsFileWriteable(s)) {
638         case 0:
639                 minibuffer->Set(_("File `")+MakeDisplayPath(s,50)+
640                                 _("' is read-only."));
641                 ro = true;
642                 // Fall through
643         case 1:
644                 b=readFile(s, ro);
645                 if (b) {
646                         b->lyxvc.file_found_hook(s);
647                 }
648                 break; //fine- it's r/w
649         case -1:
650                 // Here we probably should run
651                 if (LyXVC::file_not_found_hook(s)) {
652                         // Ask if the file should be checked out for
653                         // viewing/editing, if so: load it.
654                         lyxerr << "Do you want to checkout?" << endl;
655                 }
656                 if (AskQuestion(_("Cannot open specified file:"), 
657                                 MakeDisplayPath(s,50),
658                                 _("Create new document with this name?")))
659                 {
660                         // Find a free buffer
661                         b = newFile(s,string());
662                 }
663                 break;
664         }
666         if (b && tolastfiles)
667                 lastfiles->newFile(b->getFileName());
669         return b;