]> git.lyx.org Git - lyx.git/blob - src/bufferlist.C
edf248563faac9e4122c9c13716b0532c4a98dcb
[lyx.git] / src / bufferlist.C
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  */
14
15 #ifdef __GNUG__
16 #pragma implementation
17 #endif
18
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 "error.h"
30 #include "lyxrc.h"
31 #include "lyxscreen.h"
32 #include "lyxtext.h"
33 #include "lyx_cb.h"
34 #include "gettext.h"
35
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 &);
41
42 //
43 // Class BufferStorage
44 //
45
46 BufferStorage::BufferStorage()
47 {
48         // Initialize the buffer array
49         for (int i=NUMBER_OF_BUFFERS-1; i >=0; i--) {
50                 buffer[i] = 0;
51         }       
52 }
53
54
55 bool BufferStorage::isEmpty()
56 {
57         for (int i=NUMBER_OF_BUFFERS-1; i >=0; i--) {
58                 if (buffer[i]) return false;
59         }
60         return true;
61 }
62
63
64 void BufferStorage::release(Buffer* buf)
65 {
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;
72 }
73
74
75 Buffer* BufferStorage::newBuffer(string const &s,
76                                  LyXRC *lyxrc,
77                                  bool ronly)
78 {
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(string("Assigning to buffer ") + tostr(i), Error::ANY);
85         return buffer[i];
86 }
87
88
89 //
90 // Class BufferStrorage_Iter
91 //
92
93 Buffer* BufferStorage_Iter::operator() ()
94 {
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;       
103 }
104
105
106 Buffer* BufferStorage_Iter::operator[] (int a)
107 {
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;       
118 }
119
120
121 //
122 // Class BufferList
123 //
124 BufferList::BufferList()
125 {
126         _state = BufferList::OK;
127 }
128
129
130 BufferList::~BufferList()
131 {
132         // I am sure something should be done here too.
133 }
134
135
136 bool BufferList::isEmpty()
137 {
138         return bstore.isEmpty();
139 }
140
141 extern void MenuWrite(Buffer*);
142
143 bool BufferList::QwriteAll()
144 {
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         }
173
174         return true;
175 }
176
177
178 // Should probably be moved to somewhere else: BufferView? LyXView?
179 bool BufferList::write(Buffer *buf, bool makeBackup)
180 {
181         minibuffer->Set(_("Saving document"),
182                         MakeDisplayPath(buf->filename),"...");
183
184         // We don't need autosaves in the immediate future. (Asger)
185         buf->resetAutosaveTimers();
186
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                  */
208
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();
217
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(string("BlockSize: ") + tostr(blksize));
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.print("utime error.");
242                                 }
243                                 delete [] cbuf;
244                         } else {
245                                 lyxerr.print("LyX was not able to make backupcopy. Beware.");
246                         }
247                         delete[] times;
248                 }
249         }
250         
251         if (buf->writeFile(buf->filename,false)) {
252                 buf->markLyxClean();
253
254                 minibuffer->Set(_("Document saved as"),
255                                 MakeDisplayPath(buf->filename));
256
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         }
278
279         return true;
280 }
281
282
283 void BufferList::closeAll()
284 {
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;
293 }
294
295
296 void BufferList::resize()
297 {
298         BufferStorage_Iter biter(bstore);
299         Buffer *b=0;
300         while ((b=biter())) {
301                 b->resize();
302         }
303 }
304
305
306 bool BufferList::close(Buffer *buf)
307 {
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         }
329
330         bstore.release(buf);
331         return true;
332 }
333
334
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            */
339 {
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"));
349 }
350
351
352 Buffer* BufferList::first()
353 {
354         BufferStorage_Iter biter(bstore);
355         return biter();
356 }
357
358
359 Buffer* BufferList::getBuffer(int choice)
360 {
361         BufferStorage_Iter biter(bstore);
362         Buffer *b=0;
363         b = biter[choice];
364             
365         // Be careful, this could be 0.    
366         return b;
367 }
368
369
370 void BufferList::updateInset(Inset *inset, bool mark_dirty)
371 {
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         }
381 }
382
383
384 int BufferList::unlockInset(UpdatableInset *inset)
385 {
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;
397 }
398
399
400 void BufferList::updateIncludedTeXfiles(string const & mastertmpdir)
401 {
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         }
413 }
414
415
416 void BufferList::emergencyWriteAll()
417 {
418         BufferStorage_Iter biter(bstore);
419         Buffer *b=0;
420         while ((b=biter())) {
421                 if (!b->isLyxClean()) {
422                         bool madeit=false;
423                         
424                         lyxerr.print(_("lyx: Attempting to save"
425                                       " document ")
426                                       + b->filename
427                                       + _(" as..."));
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.print(string("  ") + tostr(i+1) + ") " + s);
448                                 
449                                 if (b->writeFile(s,true)) {
450                                         b->markLyxClean();
451                                         lyxerr.print(_("  Save seems successful. Phew."));
452                                         madeit = true;
453                                 } else if (i != 2) {
454                                         lyxerr.print(_("  Save failed! Trying..."));
455                                 } else {
456                                         lyxerr.print(_("  Save failed! Bummer. Document is lost."));
457                                 }
458                         }
459                 }
460         }
461 }
462
463
464 Buffer* BufferList::readFile(string const & s, bool ronly)
465 {
466         Buffer *b = bstore.newBuffer(s, lyxrc, ronly);
467
468         string ts = s;
469         string e = OnlyPath(s);
470         string a = e;
471         // File information about normal file
472         FileInfo fileInfo2(s);
473
474         // Check if emergency save file exists and is newer.
475         e += OnlyFilename(s) + ".emergency";
476         FileInfo fileInfoE(e);
477
478         bool use_emergency = false;
479
480         if (fileInfoE.exist() && fileInfo2.exist()) {
481                 if (fileInfoE.getModificationTime()
482                     > fileInfo2.getModificationTime()) {
483                         if (AskQuestion(_("An emergency save of this document exists!"),
484                                         MakeDisplayPath(s,50),
485                                         _("Try to load that instead?"))) {
486                                 ts = e;
487                                 // the file is not saved if we load the
488                                 // emergency file.
489                                 b->markDirty();
490                                 use_emergency = true;
491                         } else {
492                                 // Here, we should delete the emergency save
493                                 unlink(e.c_str());
494                         }
495                 }
496         }
497
498         if (!use_emergency) {
499                 // Now check if autosave file is newer.
500                 a += '#';
501                 a += OnlyFilename(s);
502                 a += '#';
503                 FileInfo fileInfoA(a);
504                 if (fileInfoA.exist() && fileInfo2.exist()) {
505                         if (fileInfoA.getModificationTime()
506                             > fileInfo2.getModificationTime()) {
507                                 if (AskQuestion(_("Autosave file is newer."),
508                                                 MakeDisplayPath(s,50),
509                                                 _("Load that one instead?"))) {
510                                         ts = a;
511                                         // the file is not saved if we load the
512                                         // autosave file.
513                                         b->markDirty();
514                                 } else {
515                                         // Here, we should delete the autosave
516                                         unlink(a.c_str());
517                                 }
518                         }
519                 }
520         }
521         // not sure if this is the correct place to begin LyXLex
522         LyXLex lex(0, 0);
523         lex.setFile(ts);
524         if (b->readFile(lex))
525                 return b;
526         else {
527                 bstore.release(b);
528                 return 0;
529         }
530 }
531
532
533 bool BufferList::exists(string const & s)
534 {
535         BufferStorage_Iter biter(bstore);
536         Buffer *b=0;
537         while ((b=biter())) {
538                 if (b->filename == s)
539                         return true;
540         }
541         return false;
542 }
543
544
545 Buffer* BufferList::getBuffer(string const &s)
546 {
547         BufferStorage_Iter biter(bstore);
548         Buffer *b=0;
549         while ((b=biter())) {
550                 if (b->filename ==s)
551                         return b;
552         }
553         return 0;
554 }
555
556
557 Buffer* BufferList::newFile(string const & name, string tname)
558 {
559         /* get a free buffer */ 
560         Buffer *b = bstore.newBuffer(name, lyxrc);
561
562         // use defaults.lyx as a default template if it exists.
563         if (tname.empty()) {
564                 tname = LibFileSearch("templates", "defaults.lyx");
565         }
566         if (!tname.empty() && IsLyXFilename(tname)){
567                 bool templateok = false;
568                 LyXLex lex(0,0);
569                 lex.setFile(tname);
570                 if (lex.IsOK()) {
571                         if (b->readFile(lex)) {
572                                 templateok = true;
573                         }
574                 }
575                 if (!templateok) {
576                         WriteAlert(_("Error!"),_("Unable to open template"), 
577                                    MakeDisplayPath(tname));
578                         // no template, start with empty buffer
579                         b->paragraph = new LyXParagraph();
580                 }
581         }
582         else {  // start with empty buffer
583                 b->paragraph = new LyXParagraph();
584         }
585
586         b->markDirty();
587         b->setReadonly(false);
588         
589         return b;
590 }
591
592
593 Buffer* BufferList::loadLyXFile(string const & filename, bool tolastfiles)
594 {
595         // make sure our path is absolute
596         string s = MakeAbsPath(filename);
597
598         // Is this done too early?
599         // Is it LinuxDoc?
600         if (IsSGMLFilename(s)) {
601                 FileInfo fi(s);
602                 if (fi.exist() && fi.readable()) {
603                         if (!RunLinuxDoc(-1, s)) {
604                                 s = ChangeExtension (s, ".lyx", false);
605                         } else { // sgml2lyx failed
606                                 WriteAlert(_("Error!"),
607                                            _("Could not convert file"),s);
608                                 return 0;
609                         }
610                 } else {
611                         // just change the extension and it will be
612                         // handled like a regular lyx file that does
613                         // not exist.
614                         s = ChangeExtension(s, ".lyx", false);
615                 }
616         }
617         
618         // file already open?
619         if (exists(s)) {
620                 if (AskQuestion(_("Document is already open:"), 
621                                 MakeDisplayPath(s,50),
622                                 _("Do you want to reload that document?"))) {
623                         // Reload is accomplished by closing and then loading
624                         if (!close(getBuffer(s))) {
625                                 return 0;
626                         }
627                         // Fall through to new load. (Asger)
628                 } else {
629                         // Here, we pretend that we just loaded the 
630                         // open document
631                         return getBuffer(s);
632                 }
633         }
634         Buffer *b=0;
635         bool ro = false;
636         switch (IsFileWriteable(s)) {
637         case 0:
638                 minibuffer->Set(_("File `")+MakeDisplayPath(s,50)+
639                                 _("' is read-only."));
640                 ro = true;
641                 // Fall through
642         case 1:
643                 b=readFile(s, ro);
644                 if (b) {
645                         b->lyxvc.file_found_hook(s);
646                 }
647                 break; //fine- it's r/w
648         case -1:
649                 // Here we probably should run
650                 if (LyXVC::file_not_found_hook(s)) {
651                         // Ask if the file should be checked out for
652                         // viewing/editing, if so: load it.
653                         lyxerr.print("Do you want to checkout?");
654                 }
655                 if (AskQuestion(_("Cannot open specified file:"), 
656                                 MakeDisplayPath(s,50),
657                                 _("Create new document with this name?")))
658                 {
659                         // Find a free buffer
660                         b = newFile(s,string());
661                 }
662                 break;
663         }
664
665         if (b && tolastfiles)
666                 lastfiles->newFile(b->getFileName());
667
668         return b;
669 }