]> git.lyx.org Git - lyx.git/blob - src/buffer_funcs.cpp
Fixed some lines that were too long. It compiled afterwards.
[lyx.git] / src / buffer_funcs.cpp
1 /**
2  * \file buffer_funcs.cpp
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  * \author Alfredo Braunstein
8  *
9  * Full author contact details are available in file CREDITS.
10  *
11  */
12
13 #include <config.h>
14
15 #include "buffer_funcs.h"
16 #include "Buffer.h"
17 #include "BufferList.h"
18 #include "BufferParams.h"
19 #include "DocIterator.h"
20 #include "Counters.h"
21 #include "ErrorList.h"
22 #include "Floating.h"
23 #include "FloatList.h"
24 #include "gettext.h"
25 #include "Language.h"
26 #include "LaTeX.h"
27 #include "TextClass.h"
28 #include "Paragraph.h"
29 #include "paragraph_funcs.h"
30 #include "ParagraphList.h"
31 #include "ParagraphParameters.h"
32 #include "ParIterator.h"
33 #include "LyXVC.h"
34 #include "TexRow.h"
35 #include "TocBackend.h"
36 #include "VCBackend.h"
37
38 #include "frontends/alert.h"
39
40 #include "insets/InsetBibitem.h"
41 #include "insets/InsetCaption.h"
42 #include "insets/InsetInclude.h"
43 #include "insets/InsetTabular.h"
44
45 #include "support/filetools.h"
46 #include "support/fs_extras.h"
47 #include "support/lyxlib.h"
48
49 #include <boost/bind.hpp>
50 #include <boost/filesystem/operations.hpp>
51
52 using std::min;
53 using std::string;
54
55
56 namespace lyx {
57
58 using namespace std;
59
60 using support::bformat;
61 using support::FileName;
62 using support::libFileSearch;
63 using support::makeAbsPath;
64 using support::makeDisplayPath;
65 using support::onlyFilename;
66 using support::onlyPath;
67 using support::unlink;
68
69 namespace Alert = frontend::Alert;
70 namespace fs = boost::filesystem;
71
72 namespace {
73
74 bool readFile(Buffer * const b, FileName const & s)
75 {
76         BOOST_ASSERT(b);
77
78         // File information about normal file
79         if (!fs::exists(s.toFilesystemEncoding())) {
80                 docstring const file = makeDisplayPath(s.absFilename(), 50);
81                 docstring text = bformat(_("The specified document\n%1$s"
82                                                      "\ncould not be read."), file);
83                 Alert::error(_("Could not read document"), text);
84                 return false;
85         }
86
87         // Check if emergency save file exists and is newer.
88         FileName const e(s.absFilename() + ".emergency");
89
90         if (fs::exists(e.toFilesystemEncoding()) &&
91             fs::exists(s.toFilesystemEncoding()) &&
92             fs::last_write_time(e.toFilesystemEncoding()) > fs::last_write_time(s.toFilesystemEncoding()))
93         {
94                 docstring const file = makeDisplayPath(s.absFilename(), 20);
95                 docstring const text =
96                         bformat(_("An emergency save of the document "
97                                   "%1$s exists.\n\n"
98                                                "Recover emergency save?"), file);
99                 switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
100                                       _("&Recover"),  _("&Load Original"),
101                                       _("&Cancel")))
102                 {
103                 case 0:
104                         // the file is not saved if we load the emergency file.
105                         b->markDirty();
106                         return b->readFile(e);
107                 case 1:
108                         break;
109                 default:
110                         return false;
111                 }
112         }
113
114         // Now check if autosave file is newer.
115         FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#');
116
117         if (fs::exists(a.toFilesystemEncoding()) &&
118             fs::exists(s.toFilesystemEncoding()) &&
119             fs::last_write_time(a.toFilesystemEncoding()) > fs::last_write_time(s.toFilesystemEncoding()))
120         {
121                 docstring const file = makeDisplayPath(s.absFilename(), 20);
122                 docstring const text =
123                         bformat(_("The backup of the document "
124                                   "%1$s is newer.\n\nLoad the "
125                                                "backup instead?"), file);
126                 switch (Alert::prompt(_("Load backup?"), text, 0, 2,
127                                       _("&Load backup"), _("Load &original"),
128                                       _("&Cancel") ))
129                 {
130                 case 0:
131                         // the file is not saved if we load the autosave file.
132                         b->markDirty();
133                         return b->readFile(a);
134                 case 1:
135                         // Here we delete the autosave
136                         unlink(a);
137                         break;
138                 default:
139                         return false;
140                 }
141         }
142         return b->readFile(s);
143 }
144
145
146 } // namespace anon
147
148
149
150 bool loadLyXFile(Buffer * b, FileName const & s)
151 {
152         BOOST_ASSERT(b);
153
154         if (fs::is_readable(s.toFilesystemEncoding())) {
155                 if (readFile(b, s)) {
156                         b->lyxvc().file_found_hook(s);
157                         if (!fs::is_writable(s.toFilesystemEncoding()))
158                                 b->setReadonly(true);
159                         return true;
160                 }
161         } else {
162                 docstring const file = makeDisplayPath(s.absFilename(), 20);
163                 // Here we probably should run
164                 if (LyXVC::file_not_found_hook(s)) {
165                         docstring const text =
166                                 bformat(_("Do you want to retrieve the document"
167                                                        " %1$s from version control?"), file);
168                         int const ret = Alert::prompt(_("Retrieve from version control?"),
169                                 text, 0, 1, _("&Retrieve"), _("&Cancel"));
170
171                         if (ret == 0) {
172                                 // How can we know _how_ to do the checkout?
173                                 // With the current VC support it has to be,
174                                 // a RCS file since CVS do not have special ,v files.
175                                 RCS::retrieve(s);
176                                 return loadLyXFile(b, s);
177                         }
178                 }
179         }
180         return false;
181 }
182
183
184 bool checkIfLoaded(FileName const & fn)
185 {
186         return theBufferList().getBuffer(fn.absFilename());
187 }
188
189
190 Buffer * checkAndLoadLyXFile(FileName const & filename)
191 {
192         // File already open?
193         Buffer * checkBuffer = theBufferList().getBuffer(filename.absFilename());
194         if (checkBuffer) {
195                 if (checkBuffer->isClean())
196                         return checkBuffer;
197                 docstring const file = makeDisplayPath(filename.absFilename(), 20);
198                 docstring text = bformat(_(
199                                 "The document %1$s is already loaded and has unsaved changes.\n"
200                                 "Do you want to abandon your changes and reload the version on disk?"), file);
201                 if (Alert::prompt(_("Reload saved document?"),
202                                 text, 0, 1,  _("&Reload"), _("&Keep Changes")))
203                         return checkBuffer;
204
205                 // FIXME: should be LFUN_REVERT
206                 if (theBufferList().close(checkBuffer, false))
207                         // Load it again.
208                         return checkAndLoadLyXFile(filename);
209                 else
210                         // The file could not be closed.
211                         return 0;
212         }
213
214         if (isFileReadable(filename)) {
215                 Buffer * b = theBufferList().newBuffer(filename.absFilename());
216                 if (!lyx::loadLyXFile(b, filename)) {
217                         theBufferList().release(b);
218                         return 0;
219                 }
220                 return b;
221         }
222
223         docstring text = bformat(_("The document %1$s does not yet "
224                 "exist.\n\nDo you want to create a new document?"),
225                 from_utf8(filename.absFilename()));
226         if (!Alert::prompt(_("Create new document?"),
227                         text, 0, 1, _("&Create"), _("Cancel")))
228                 return newFile(filename.absFilename(), string(), true);
229
230         return 0;
231 }
232
233 // FIXME newFile() should probably be a member method of Application...
234 Buffer * newFile(string const & filename, string const & templatename,
235                  bool const isNamed)
236 {
237         // get a free buffer
238         Buffer * b = theBufferList().newBuffer(filename);
239         BOOST_ASSERT(b);
240
241         FileName tname;
242         // use defaults.lyx as a default template if it exists.
243         if (templatename.empty())
244                 tname = libFileSearch("templates", "defaults.lyx");
245         else
246                 tname = makeAbsPath(templatename);
247
248         if (!tname.empty()) {
249                 if (!b->readFile(tname)) {
250                         docstring const file = makeDisplayPath(tname.absFilename(), 50);
251                         docstring const text  = bformat(
252                                 _("The specified document template\n%1$s\ncould not be read."),
253                                 file);
254                         Alert::error(_("Could not read template"), text);
255                         theBufferList().release(b);
256                         return 0;
257                 }
258         }
259
260         if (!isNamed) {
261                 b->setUnnamed();
262                 b->setFileName(filename);
263         }
264
265         b->setReadonly(false);
266         b->fully_loaded(true);
267
268         return b;
269 }
270
271
272 void bufferErrors(Buffer const & buf, TeXErrors const & terr,
273                                   ErrorList & errorList)
274 {
275         TeXErrors::Errors::const_iterator cit = terr.begin();
276         TeXErrors::Errors::const_iterator end = terr.end();
277
278         for (; cit != end; ++cit) {
279                 int id_start = -1;
280                 int pos_start = -1;
281                 int errorrow = cit->error_in_line;
282                 bool found = buf.texrow().getIdFromRow(errorrow, id_start,
283                                                        pos_start);
284                 int id_end = -1;
285                 int pos_end = -1;
286                 do {
287                         ++errorrow;
288                         found = buf.texrow().getIdFromRow(errorrow, id_end,
289                                                           pos_end);
290                 } while (found && id_start == id_end && pos_start == pos_end);
291
292                 errorList.push_back(ErrorItem(cit->error_desc,
293                         cit->error_text, id_start, pos_start, pos_end));
294         }
295 }
296
297
298 string const bufferFormat(Buffer const & buffer)
299 {
300         if (buffer.isDocBook())
301                 return "docbook";
302         else if (buffer.isLiterate())
303                 return "literate";
304         else
305                 return "latex";
306 }
307
308
309 int countWords(DocIterator const & from, DocIterator const & to)
310 {
311         int count = 0;
312         bool inword = false;
313         for (DocIterator dit = from ; dit != to ; dit.forwardPos()) {
314                 // Copied and adapted from isLetter() in ControlSpellChecker
315                 if (dit.inTexted()
316                     && dit.pos() != dit.lastpos()
317                     && dit.paragraph().isLetter(dit.pos())
318                     && !dit.paragraph().isDeleted(dit.pos())) {
319                         if (!inword) {
320                                 ++count;
321                                 inword = true;
322                         }
323                 } else if (inword)
324                         inword = false;
325         }
326
327         return count;
328 }
329
330
331 namespace {
332
333 depth_type getDepth(DocIterator const & it)
334 {
335         depth_type depth = 0;
336         for (size_t i = 0 ; i < it.depth() ; ++i)
337                 if (!it[i].inset().inMathed())
338                         depth += it[i].paragraph().getDepth() + 1;
339         // remove 1 since the outer inset does not count
340         return depth - 1;
341 }
342
343 depth_type getItemDepth(ParIterator const & it)
344 {
345         Paragraph const & par = *it;
346         LYX_LABEL_TYPES const labeltype = par.layout()->labeltype;
347
348         if (labeltype != LABEL_ENUMERATE && labeltype != LABEL_ITEMIZE)
349                 return 0;
350
351         // this will hold the lowest depth encountered up to now.
352         depth_type min_depth = getDepth(it);
353         ParIterator prev_it = it;
354         while (true) {
355                 if (prev_it.pit())
356                         --prev_it.top().pit();
357                 else {
358                         // start of nested inset: go to outer par
359                         prev_it.pop_back();
360                         if (prev_it.empty()) {
361                                 // start of document: nothing to do
362                                 return 0;
363                         }
364                 }
365
366                 // We search for the first paragraph with same label
367                 // that is not more deeply nested.
368                 Paragraph & prev_par = *prev_it;
369                 depth_type const prev_depth = getDepth(prev_it);
370                 if (labeltype == prev_par.layout()->labeltype) {
371                         if (prev_depth < min_depth) {
372                                 return prev_par.itemdepth + 1;
373                         }
374                         else if (prev_depth == min_depth) {
375                                 return prev_par.itemdepth;
376                         }
377                 }
378                 min_depth = std::min(min_depth, prev_depth);
379                 // small optimization: if we are at depth 0, we won't
380                 // find anything else
381                 if (prev_depth == 0) {
382                         return 0;
383                 }
384         }
385 }
386
387
388 bool needEnumCounterReset(ParIterator const & it)
389 {
390         Paragraph const & par = *it;
391         BOOST_ASSERT(par.layout()->labeltype == LABEL_ENUMERATE);
392         depth_type const cur_depth = par.getDepth();
393         ParIterator prev_it = it;
394         while (prev_it.pit()) {
395                 --prev_it.top().pit();
396                 Paragraph const & prev_par = *prev_it;
397                 if (prev_par.getDepth() <= cur_depth)
398                         return  prev_par.layout()->labeltype != LABEL_ENUMERATE;
399         }
400         // start of nested inset: reset
401         return true;
402 }
403
404
405 void setCaptionLabels(Inset & inset, string const & type,
406                 docstring const label, Counters & counters)
407 {
408         Text * text = inset.getText(0);
409         if (!text)
410                 return;
411
412         ParagraphList & pars = text->paragraphs();
413         if (pars.empty())
414                 return;
415
416         docstring const counter = from_ascii(type);
417
418         ParagraphList::iterator p = pars.begin();
419         for (; p != pars.end(); ++p) {
420                 InsetList::iterator it2 = p->insetlist.begin();
421                 InsetList::iterator end2 = p->insetlist.end();
422                 // Any caption within this float should have the same
423                 // label prefix but different numbers.
424                 for (; it2 != end2; ++it2) {
425                         Inset & icap = *it2->inset;
426                         // Look deeper just in case.
427                         setCaptionLabels(icap, type, label, counters);
428                         if (icap.lyxCode() == Inset::CAPTION_CODE) {
429                                 // We found a caption!
430                                 counters.step(counter);
431                                 int number = counters.value(counter);
432                                 InsetCaption & ic = static_cast<InsetCaption &>(icap);
433                                 ic.setType(type);
434                                 ic.setCount(number);
435                                 ic.setCustomLabel(label);
436                         }
437                 }
438         }
439 }
440
441
442 void setCaptions(Paragraph & par, TextClass const & textclass)
443 {
444         if (par.insetlist.empty())
445                 return;
446
447         Counters & counters = textclass.counters();
448
449         InsetList::iterator it = par.insetlist.begin();
450         InsetList::iterator end = par.insetlist.end();
451         for (; it != end; ++it) {
452                 Inset & inset = *it->inset;
453                 if (inset.lyxCode() == Inset::FLOAT_CODE
454                         || inset.lyxCode() == Inset::WRAP_CODE) {
455                         docstring const name = inset.name();
456                         if (name.empty())
457                                 continue;
458
459                         Floating const & fl = textclass.floats().getType(to_ascii(name));
460                         // FIXME UNICODE
461                         string const & type = fl.type();
462                         docstring const label = from_utf8(fl.name());
463                         setCaptionLabels(inset, type, label, counters);
464                 }
465                 else if (inset.lyxCode() == Inset::TABULAR_CODE
466                         &&  static_cast<InsetTabular &>(inset).tabular.isLongTabular()) {
467                         // FIXME: are "table" and "Table" the correct type and label?
468                         setCaptionLabels(inset, "table", from_ascii("Table"), counters);
469                 }
470                 else if (inset.lyxCode() == Inset::LISTINGS_CODE)
471                         setCaptionLabels(inset, "listing", from_ascii("Listing"), counters);
472                 else if (inset.lyxCode() == Inset::INCLUDE_CODE)
473                         // if this include inset contains lstinputlisting, and has a caption
474                         // it will increase the 'listing' counter by one
475                         static_cast<InsetInclude &>(inset).updateCounter(counters);
476         }
477 }
478
479 // set the label of a paragraph. This includes the counters.
480 void setLabel(Buffer const & buf, ParIterator & it, TextClass const & textclass)
481 {
482         Paragraph & par = *it;
483         Layout_ptr const & layout = par.layout();
484         Counters & counters = textclass.counters();
485
486         if (par.params().startOfAppendix()) {
487                 // FIXME: only the counter corresponding to toplevel
488                 // sectionning should be reset
489                 counters.reset();
490                 counters.appendix(true);
491         }
492         par.params().appendix(counters.appendix());
493
494         // Compute the item depth of the paragraph
495         par.itemdepth = getItemDepth(it);
496
497         if (layout->margintype == MARGIN_MANUAL) {
498                 if (par.params().labelWidthString().empty())
499                         par.params().labelWidthString(par.translateIfPossible(layout->labelstring(), buf.params()));
500         } else {
501                 par.params().labelWidthString(docstring());
502         }
503
504         // Optimisation: setLabel() can be called for each for each
505         // paragraph of the document. So we make the string static to
506         // avoid the repeated instanciation.
507         static docstring itemlabel;
508
509         // is it a layout that has an automatic label?
510         if (layout->labeltype == LABEL_COUNTER) {
511                 if (layout->toclevel <= buf.params().secnumdepth
512                     && (layout->latextype != LATEX_ENVIRONMENT
513                         || isFirstInSequence(it.pit(), it.plist()))) {
514                         counters.step(layout->counter);
515                         par.params().labelString(
516                                 par.expandLabel(layout, buf.params()));
517                 } else
518                         par.params().labelString(docstring());
519
520         } else if (layout->labeltype == LABEL_ITEMIZE) {
521                 // At some point of time we should do something more
522                 // clever here, like:
523                 //   par.params().labelString(
524                 //    buf.params().user_defined_bullet(par.itemdepth).getText());
525                 // for now, use a simple hardcoded label
526                 switch (par.itemdepth) {
527                 case 0:
528                         itemlabel = char_type(0x2022);
529                         break;
530                 case 1:
531                         itemlabel = char_type(0x2013);
532                         break;
533                 case 2:
534                         itemlabel = char_type(0x2217);
535                         break;
536                 case 3:
537                         itemlabel = char_type(0x2219); // or 0x00b7
538                         break;
539                 }
540                 par.params().labelString(itemlabel);
541
542         } else if (layout->labeltype == LABEL_ENUMERATE) {
543                 // FIXME
544                 // Yes I know this is a really, really! bad solution
545                 // (Lgb)
546                 docstring enumcounter = from_ascii("enum");
547
548                 switch (par.itemdepth) {
549                 case 2:
550                         enumcounter += 'i';
551                 case 1:
552                         enumcounter += 'i';
553                 case 0:
554                         enumcounter += 'i';
555                         break;
556                 case 3:
557                         enumcounter += "iv";
558                         break;
559                 default:
560                         // not a valid enumdepth...
561                         break;
562                 }
563
564                 // Maybe we have to reset the enumeration counter.
565                 if (needEnumCounterReset(it))
566                         counters.reset(enumcounter);
567
568                 counters.step(enumcounter);
569
570                 string format;
571
572                 switch (par.itemdepth) {
573                 case 0:
574                         format = N_("\\arabic{enumi}.");
575                         break;
576                 case 1:
577                         format = N_("(\\alph{enumii})");
578                         break;
579                 case 2:
580                         format = N_("\\roman{enumiii}.");
581                         break;
582                 case 3:
583                         format = N_("\\Alph{enumiv}.");
584                         break;
585                 default:
586                         // not a valid enumdepth...
587                         break;
588                 }
589
590                 par.params().labelString(counters.counterLabel(
591                         par.translateIfPossible(from_ascii(format), buf.params())));
592
593         } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
594                 counters.step(from_ascii("bibitem"));
595                 int number = counters.value(from_ascii("bibitem"));
596                 if (par.bibitem())
597                         par.bibitem()->setCounter(number);
598
599                 par.params().labelString(
600                         par.translateIfPossible(layout->labelstring(), buf.params()));
601                 // In biblio shouldn't be following counters but...
602         } else if (layout->labeltype == LABEL_SENSITIVE) {
603                 // Search for the first float or wrap inset in the iterator
604                 size_t i = it.depth();
605                 Inset * in = 0;
606                 while (i > 0) {
607                         --i;
608                         Inset::Code const code = it[i].inset().lyxCode();
609                         if (code == Inset::FLOAT_CODE ||
610                             code == Inset::WRAP_CODE) {
611                                 in = &it[i].inset();
612                                 break;
613                         }
614                 }
615                 // FIXME Can Inset::name() return an empty name for wide or
616                 // float insets? If not we can put the definition of type
617                 // inside the if (in) clause and use that instead of
618                 // if (!type.empty()).
619                 docstring type;
620                 if (in)
621                         type = in->name();
622
623                 if (!type.empty()) {
624                         Floating const & fl = textclass.floats().getType(to_ascii(type));
625                         // FIXME UNICODE
626                         counters.step(from_ascii(fl.type()));
627
628                         // Doesn't work... yet.
629                         par.params().labelString(par.translateIfPossible(
630                                 bformat(from_ascii("%1$s #:"), from_utf8(fl.name())),
631                                 buf.params()));
632                 } else {
633                         // par->SetLayout(0);
634                         par.params().labelString(par.translateIfPossible(
635                                 layout->labelstring(), buf.params()));
636                 }
637
638         } else if (layout->labeltype == LABEL_NO_LABEL)
639                 par.params().labelString(docstring());
640         else
641                 par.params().labelString(
642                         par.translateIfPossible(layout->labelstring(), buf.params()));
643 }
644
645 } // anon namespace
646
647
648 void updateLabels(Buffer const & buf, bool childonly)
649 {
650         Buffer const * const master = buf.getMasterBuffer();
651         // Use the master text class also for child documents
652         TextClass const & textclass = master->params().getTextClass();
653
654         if (!childonly) {
655                 // If this is a child document start with the master
656                 if (master != &buf) {
657                         updateLabels(*master);
658                         return;
659                 }
660
661                 // start over the counters
662                 textclass.counters().reset();
663         }
664
665         ParIterator const end = par_iterator_end(buf.inset());
666
667         for (ParIterator it = par_iterator_begin(buf.inset()); it != end; ++it) {
668                 // reduce depth if necessary
669                 if (it.pit()) {
670                         Paragraph const & prevpar = it.plist()[it.pit() - 1];
671                         it->params().depth(min(it->params().depth(),
672                                                prevpar.getMaxDepthAfter()));
673                 } else
674                         it->params().depth(0);
675
676                 // set the counter for this paragraph
677                 setLabel(buf, it, textclass);
678
679                 // It is better to set the captions after setLabel because
680                 // the caption number might need the section number in the
681                 // future.
682                 setCaptions(*it, textclass);
683
684                 // Now included docs
685                 InsetList::const_iterator iit = it->insetlist.begin();
686                 InsetList::const_iterator end = it->insetlist.end();
687                 for (; iit != end; ++iit) {
688                         if (iit->inset->lyxCode() == Inset::INCLUDE_CODE)
689                                 static_cast<InsetInclude const *>(iit->inset)
690                                         ->updateLabels(buf);
691                 }
692         }
693
694         Buffer & cbuf = const_cast<Buffer &>(buf);
695         cbuf.tocBackend().update();
696         if (!childonly)
697                 cbuf.structureChanged();
698 }
699
700
701 void checkBufferStructure(Buffer & buffer, ParIterator const & par_it)
702 {
703         if (par_it->layout()->toclevel != Layout::NOT_IN_TOC) {
704                 Buffer * master = buffer.getMasterBuffer();
705                 master->tocBackend().updateItem(par_it);
706                 master->structureChanged();
707         }
708 }
709
710 } // namespace lyx