]> git.lyx.org Git - lyx.git/blob - src/bufferlist.C
discard changes
[lyx.git] / src / bufferlist.C
1 /**
2  * \file bufferlist.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  *
8  * Full author contact details are available in file CREDITS
9  */
10
11 #include <config.h>
12
13 #include "bufferlist.h"
14 #include "lyx_main.h"
15 #include "lastfiles.h"
16 #include "debug.h"
17 #include "lyxrc.h"
18 #include "lyxtext.h"
19 #include "lyx_cb.h"
20 #include "bufferview_funcs.h"
21 #include "BufferView.h"
22 #include "gettext.h"
23 #include "frontends/LyXView.h"
24 #include "vc-backend.h"
25 #include "TextCache.h"
26 #include "lyxlex.h"
27
28 #include "frontends/Alert.h"
29
30 #include "support/FileInfo.h"
31 #include "support/filetools.h"
32 #include "support/lyxmanip.h"
33 #include "support/lyxfunctional.h"
34 #include "support/LAssert.h"
35
36 #include <boost/bind.hpp>
37 #include "support/BoostFormat.h"
38
39 #include <cassert>
40 #include <algorithm>
41 #include <functional>
42
43
44 using std::vector;
45 using std::find;
46 using std::endl;
47 using std::find_if;
48 using std::for_each;
49 using std::mem_fun;
50
51 extern BufferView * current_view;
52
53
54 BufferList::BufferList()
55 {}
56
57
58 bool BufferList::empty() const
59 {
60         return bstore.empty();
61 }
62
63
64 bool BufferList::quitWriteBuffer(Buffer * buf)
65 {
66         string file;
67         if (buf->isUnnamed())
68                 file = OnlyFilename(buf->fileName());
69         else
70                 file = MakeDisplayPath(buf->fileName(), 30);
71
72 #if USE_BOOST_FORMAT
73         boost::format fmt(_("The document %1$s has unsaved changes.\n\nDo you want to save the document?"));
74         fmt % file;
75         string text = fmt.str();
76 #else
77         string text = _("The document ");
78         text += file + _(" has unsaved changes.\n\nWhat do you want to do with it?");
79 #endif
80         int const ret = Alert::prompt(_("Save changed document?"),
81                 text, 0, 2, _("&Save Changes"), _("&Discard Changes"), _("&Cancel"));
82
83         if (ret == 0) {
84                 // FIXME: WriteAs can be asynch !
85                 // but not right now...maybe we should remove that
86
87                 bool succeeded;
88
89                 if (buf->isUnnamed())
90                         succeeded = WriteAs(current_view, buf);
91                 else
92                         succeeded = MenuWrite(current_view, buf);
93
94                 if (!succeeded)
95                         return false;
96         } else if (ret == 1) {
97                 // if we crash after this we could
98                 // have no autosave file but I guess
99                 // this is really inprobable (Jug)
100                 if (buf->isUnnamed())
101                         removeAutosaveFile(buf->fileName());
102
103         } else {
104                 return false;
105         }
106
107         return true;
108 }
109
110
111 bool BufferList::quitWriteAll()
112 {
113         BufferStorage::iterator it = bstore.begin();
114         BufferStorage::iterator end = bstore.end();
115         for (; it != end; ++it) {
116                 if ((*it)->isClean())
117                         continue;
118
119                 if (!quitWriteBuffer(*it))
120                         return false;
121         }
122
123         return true;
124 }
125
126
127 void BufferList::release(Buffer * buf)
128 {
129         lyx::Assert(buf);
130         BufferStorage::iterator it = find(bstore.begin(), bstore.end(), buf);
131         if (it != bstore.end()) {
132                 // Make sure that we don't store a LyXText in
133                 // the textcache that points to the buffer
134                 // we just deleted.
135                 Buffer * tmp = (*it);
136                 bstore.erase(it);
137                 textcache.removeAllWithBuffer(tmp);
138                 delete tmp;
139         }
140 }
141
142
143 Buffer * BufferList::newBuffer(string const & s, bool ronly)
144 {
145         Buffer * tmpbuf = new Buffer(s, ronly);
146         tmpbuf->params.useClassDefaults();
147         lyxerr[Debug::INFO] << "Assigning to buffer "
148                             << bstore.size() << endl;
149         bstore.push_back(tmpbuf);
150         return tmpbuf;
151 }
152
153
154 void BufferList::closeAll()
155 {
156         // Since we are closing we can just as well delete all
157         // in the textcache this will also speed the closing/quiting up a bit.
158         textcache.clear();
159
160         while (!bstore.empty()) {
161                 close(bstore.front(), false);
162         }
163 }
164
165
166 bool BufferList::close(Buffer * buf, bool ask)
167 {
168         lyx::Assert(buf);
169
170         // FIXME: is the quitting check still necessary ?
171         if (!ask || buf->isClean() || quitting || buf->paragraphs.empty()) {
172                 release(buf);
173                 return true;
174         }
175
176         string fname;
177         if (buf->isUnnamed())
178                 fname = OnlyFilename(buf->fileName());
179         else
180                 fname = MakeDisplayPath(buf->fileName(), 30);
181 #if USE_BOOST_FORMAT
182         boost::format fmt(_("The document %1$s has unsaved changes.\n\nDo you want to save the document?"));
183         fmt % fname;
184         string text = fmt.str();
185 #else
186         string text = _("The document ");
187         text += fname + _(" has unsaved changes.\n\nWhat do you want to do with it?");
188 #endif
189         int const ret = Alert::prompt(_("Save changed document?"),
190                 text, 0, 2, _("&Save Changes"), _("&Discard Changes"), _("&Cancel"));
191
192         if (ret == 0) {
193                 if (buf->isUnnamed()) {
194                         if (!WriteAs(current_view, buf))
195                                 return false;
196                 } else if (buf->save()) {
197                         lastfiles->newFile(buf->fileName());
198                 } else {
199                         return false;
200                 }
201         } else if (ret == 2) {
202                 return false;
203         }
204
205         if (buf->isUnnamed()) {
206                 removeAutosaveFile(buf->fileName());
207         }
208
209         release(buf);
210         return true;
211 }
212
213
214 vector<string> const BufferList::getFileNames() const
215 {
216         vector<string> nvec;
217         std::copy(bstore.begin(), bstore.end(),
218                   lyx::back_inserter_fun(nvec, &Buffer::fileName));
219         return nvec;
220 }
221
222
223 Buffer * BufferList::first()
224 {
225         if (bstore.empty())
226                 return 0;
227         return bstore.front();
228 }
229
230
231 Buffer * BufferList::getBuffer(unsigned int choice)
232 {
233         if (choice >= bstore.size())
234                 return 0;
235         return bstore[choice];
236 }
237
238
239 void BufferList::updateIncludedTeXfiles(string const & mastertmpdir)
240 {
241         BufferStorage::iterator it = bstore.begin();
242         BufferStorage::iterator end = bstore.end();
243         for (; it != end; ++it) {
244                 if (!(*it)->isDepClean(mastertmpdir)) {
245                         string writefile = mastertmpdir;
246                         writefile += '/';
247                         writefile += (*it)->getLatexName();
248                         (*it)->makeLaTeXFile(writefile, mastertmpdir,
249                                              false, true);
250                         (*it)->markDepClean(mastertmpdir);
251                 }
252         }
253 }
254
255
256 void BufferList::emergencyWriteAll()
257 {
258         for_each(bstore.begin(), bstore.end(),
259                  boost::bind(&BufferList::emergencyWrite, this, _1));
260 }
261
262
263 void BufferList::emergencyWrite(Buffer * buf)
264 {
265         // assert(buf) // this is not good since C assert takes an int
266                        // and a pointer is a long (JMarc)
267         assert(buf != 0); // use c assert to avoid a loop
268
269
270         // No need to save if the buffer has not changed.
271         if (buf->isClean())
272                 return;
273
274         string const doc = buf->isUnnamed()
275                 ? OnlyFilename(buf->fileName()) : buf->fileName();
276
277 #if USE_BOOST_FORMAT
278         lyxerr << boost::format(_("LyX: Attempting to save document %1$s"))
279                 % doc
280                << endl;
281 #else
282         lyxerr << _("LyX: Attempting to save document ") << doc << endl;
283 #endif
284         // We try to save three places:
285
286         // 1) Same place as document. Unless it is an unnamed doc.
287         if (!buf->isUnnamed()) {
288                 string s = buf->fileName();
289                 s += ".emergency";
290                 lyxerr << "  " << s << endl;
291                 if (buf->writeFile(s)) {
292                         buf->markClean();
293                         lyxerr << _("  Save seems successful. Phew.") << endl;
294                         return;
295                 } else {
296                         lyxerr << _("  Save failed! Trying...") << endl;
297                 }
298         }
299
300         // 2) In HOME directory.
301         string s = AddName(GetEnvPath("HOME"), buf->fileName());
302         s += ".emergency";
303         lyxerr << ' ' << s << endl;
304         if (buf->writeFile(s)) {
305                 buf->markClean();
306                 lyxerr << _("  Save seems successful. Phew.") << endl;
307                 return;
308         }
309
310         lyxerr << _("  Save failed! Trying...") << endl;
311
312         // 3) In "/tmp" directory.
313         // MakeAbsPath to prepend the current
314         // drive letter on OS/2
315         s = AddName(MakeAbsPath("/tmp/"), buf->fileName());
316         s += ".emergency";
317         lyxerr << ' ' << s << endl;
318         if (buf->writeFile(s)) {
319                 buf->markClean();
320                 lyxerr << _("  Save seems successful. Phew.") << endl;
321                 return;
322         }
323         lyxerr << _("  Save failed! Bummer. Document is lost.") << endl;
324 }
325
326
327
328 Buffer * BufferList::readFile(string const & s, bool ronly)
329 {
330         string ts(s);
331         string e = OnlyPath(s);
332         string a = e;
333         // File information about normal file
334         FileInfo fileInfo2(s);
335
336         if (!fileInfo2.exist()) {
337                 string const file = MakeDisplayPath(s, 50);
338 #if USE_BOOST_FORMAT
339                 boost::format fmt(_("The specified document\n%1$s\ncould not be read."));
340                 fmt % file;
341                 string text = fmt.str();
342 #else
343                 string text = _("The specified document\n");
344                 text += file + _(" could not be read.");
345 #endif
346                 Alert::error(_("Could not read document"), text);
347                 return 0;
348         }
349
350         Buffer * b = newBuffer(s, ronly);
351
352         // Check if emergency save file exists and is newer.
353         e += OnlyFilename(s) + ".emergency";
354         FileInfo fileInfoE(e);
355
356         bool use_emergency = false;
357
358         if (fileInfoE.exist() && fileInfo2.exist()) {
359                 if (fileInfoE.getModificationTime()
360                     > fileInfo2.getModificationTime()) {
361                         string const file = MakeDisplayPath(s, 20);
362 #if USE_BOOST_FORMAT
363                         boost::format fmt(_("An emergency save of the document %1$s exists.\n\nRecover emergency save?"));
364                         fmt % file;
365                         string text = fmt.str();
366 #else
367                         string text = _("An emergency save of the document ");
368                         text += file + _(" exists.\n\nRecover emergency save?");
369 #endif
370                         int const ret = Alert::prompt(_("Load emergency save?"),
371                                 text, 0, 1, _("&Recover"), _("&Load Original"));
372
373                         if (ret == 0) {
374                                 ts = e;
375                                 // the file is not saved if we load the
376                                 // emergency file.
377                                 b->markDirty();
378                                 use_emergency = true;
379                         } else {
380                                 // Here, we should delete the emergency save
381                                 lyx::unlink(e);
382                         }
383                 }
384         }
385
386         if (!use_emergency) {
387                 // Now check if autosave file is newer.
388                 a += '#';
389                 a += OnlyFilename(s);
390                 a += '#';
391                 FileInfo fileInfoA(a);
392                 if (fileInfoA.exist() && fileInfo2.exist()) {
393                         if (fileInfoA.getModificationTime()
394                             > fileInfo2.getModificationTime()) {
395                                 string const file = MakeDisplayPath(s, 20);
396 #if USE_BOOST_FORMAT
397                                 boost::format fmt(_("The backup of the document %1$s is newer.\n\nLoad the backup instead?"));
398                                 fmt % file;
399                                 string text = fmt.str();
400 #else
401                                 string text = _("The backup of the document ");
402                                 text += file + _(" is newer.\n\nLoad the backup instead?");
403 #endif
404                                 int const ret = Alert::prompt(_("Load backup?"),
405                                         text, 0, 1, _("&Load backup"), _("Load &original"));
406
407                                 if (ret == 0) {
408                                         ts = a;
409                                         // the file is not saved if we load the
410                                         // autosave file.
411                                         b->markDirty();
412                                 } else {
413                                         // Here, we should delete the autosave
414                                         lyx::unlink(a);
415                                 }
416                         }
417                 }
418         }
419         // not sure if this is the correct place to begin LyXLex
420         LyXLex lex(0, 0);
421         lex.setFile(ts);
422         if (b->readFile(lex, ts))
423                 return b;
424         else {
425                 release(b);
426                 return 0;
427         }
428 }
429
430
431 bool BufferList::exists(string const & s) const
432 {
433         return find_if(bstore.begin(), bstore.end(),
434                        lyx::compare_memfun(&Buffer::fileName, s))
435                 != bstore.end();
436 }
437
438
439 bool BufferList::isLoaded(Buffer const * b) const
440 {
441         lyx::Assert(b);
442
443         BufferStorage::const_iterator cit =
444                 find(bstore.begin(), bstore.end(), b);
445         return cit != bstore.end();
446 }
447
448
449 Buffer * BufferList::getBuffer(string const & s)
450 {
451         BufferStorage::iterator it =
452                 find_if(bstore.begin(), bstore.end(),
453                         lyx::compare_memfun(&Buffer::fileName, s));
454         return it != bstore.end() ? (*it) : 0;
455 }
456
457
458 Buffer * BufferList::newFile(string const & name, string tname, bool isNamed)
459 {
460         // get a free buffer
461         Buffer * b = newBuffer(name);
462
463         // use defaults.lyx as a default template if it exists.
464         if (tname.empty()) {
465                 tname = LibFileSearch("templates", "defaults.lyx");
466         }
467         if (!tname.empty()) {
468                 bool templateok = false;
469                 LyXLex lex(0, 0);
470                 lex.setFile(tname);
471                 if (lex.isOK()) {
472                         if (b->readFile(lex, tname)) {
473                                 templateok = true;
474                         }
475                 }
476                 if (!templateok) {
477                         string const file = MakeDisplayPath(tname, 50);
478 #if USE_BOOST_FORMAT
479                         boost::format fmt(_("The specified document template\n%1$s\ncould not be read."));
480                         fmt % file;
481                         string text = fmt.str();
482 #else
483                         string text = _("The specified document template\n");
484                         text += file + _(" could not be read.");
485 #endif
486                         Alert::error(_("Could not read template"), text);
487                         // no template, start with empty buffer
488                         b->paragraphs.set(new Paragraph);
489                         b->paragraphs.begin()->layout(b->params.getLyXTextClass().defaultLayout());
490                 }
491         } else {  // start with empty buffer
492                 b->paragraphs.set(new Paragraph);
493                 b->paragraphs.begin()->layout(b->params.getLyXTextClass().defaultLayout());
494         }
495
496         if (!isNamed) {
497                 b->setUnnamed();
498                 b->setFileName(name);
499         }
500
501         b->setReadonly(false);
502         b->updateDocLang(b->params.language);
503         
504         return b;
505 }
506
507
508 Buffer * BufferList::loadLyXFile(string const & filename, bool tolastfiles)
509 {
510         // get absolute path of file and add ".lyx" to the filename if
511         // necessary
512         string s = FileSearch(string(), filename, "lyx");
513         if (s.empty()) {
514                 s = filename;
515         }
516
517         // file already open?
518         if (exists(s)) {
519                 string const file = MakeDisplayPath(s, 20);
520 #if USE_BOOST_FORMAT
521                 boost::format fmt(_("The document %1$s is already loaded.\n\nDo you want to revert to the saved version?"));
522                 fmt % file;
523                 string text = fmt.str();
524 #else
525                 string text = _("The document ");
526                 text += file + _(" is already loaded.\n\nDo you want to revert to the saved version?");
527 #endif
528                 int const ret = Alert::prompt(_("Revert to saved document?"),
529                         text, 0, 1,  _("&Revert"), _("&Switch to document"));
530
531                 if (ret == 0) {
532                         // FIXME: should be LFUN_REVERT
533                         if (!close(getBuffer(s), false)) {
534                                 return 0;
535                         }
536                         // Fall through to new load. (Asger)
537                 } else {
538                         // Here, we pretend that we just loaded the
539                         // open document
540                         return getBuffer(s);
541                 }
542         }
543
544         Buffer * b = 0;
545         bool ro = false;
546         switch (IsFileWriteable(s)) {
547         case 0:
548                 ro = true;
549                 // Fall through
550         case 1:
551                 b = readFile(s, ro);
552                 if (b) {
553                         b->lyxvc.file_found_hook(s);
554                 }
555                 break; //fine- it's r/w
556         case -1: {
557                 string const file = MakeDisplayPath(s, 20);
558                 // Here we probably should run
559                 if (LyXVC::file_not_found_hook(s)) {
560 #if USE_BOOST_FORMAT
561                         boost::format fmt(_("Do you want to retrieve the document %1$s from version control?"));
562                         fmt % file;
563                         string text = fmt.str();
564 #else
565                         string text = _("Do you want to retrieve the document ");
566                         text += file + _(" from version control?");
567 #endif
568                         int const ret = Alert::prompt(_("Retrieve from version control?"),
569                                 text, 0, 1, _("&Retrieve"), _("&Cancel"));
570
571                         if (ret == 0) {
572                                 // How can we know _how_ to do the checkout?
573                                 // With the current VC support it has to be,
574                                 // a RCS file since CVS do not have special ,v files.
575                                 RCS::retrieve(s);
576                                 return loadLyXFile(filename, tolastfiles);
577                         }
578                 }
579
580 #if USE_BOOST_FORMAT
581                 boost::format fmt(_("The document %1$s does not yet exist.\n\nDo you want to create a new document?"));
582                 fmt % file;
583                 string text = fmt.str();
584 #else
585                 string text = _("The document ");
586                 text += file + _(" does not yet exist.\n\nDo you want to create a new document?");
587 #endif
588                 int const ret = Alert::prompt(_("Create new document?"),
589                         text, 0, 1, _("&Create"), _("Cancel"));
590
591                 if (ret == 0)
592                         b = newFile(s, string(), true);
593
594                 break;
595                 }
596         }
597
598         if (b && tolastfiles)
599                 lastfiles->newFile(b->fileName());
600
601         return b;
602 }
603
604
605 void BufferList::setCurrentAuthor(string const & name, string const & email)
606 {
607         BufferStorage::iterator it = bstore.begin();
608         BufferStorage::iterator end = bstore.end();
609         for (; it != end; ++it) {
610                 (*it)->authors().record(0, Author(name, email));
611         }
612 }