]> git.lyx.org Git - lyx.git/blob - src/buffer_funcs.C
Preliminary longtable caption support. If there is a caption in the first cell of...
[lyx.git] / src / buffer_funcs.C
1 /**
2  * \file buffer_funcs.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  * \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 "lyxtextclass.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 "vc-backend.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
53 namespace lyx {
54
55 using namespace std;
56
57 using support::bformat;
58 using support::FileName;
59 using support::libFileSearch;
60 using support::makeAbsPath;
61 using support::makeDisplayPath;
62 using support::onlyFilename;
63 using support::onlyPath;
64 using support::unlink;
65
66 using std::min;
67 using std::string;
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 // FIXME newFile() should probably be a member method of Application...
184 Buffer * newFile(string const & filename, string const & templatename,
185                  bool const isNamed)
186 {
187         // get a free buffer
188         Buffer * b = theBufferList().newBuffer(filename);
189         BOOST_ASSERT(b);
190
191         FileName tname;
192         // use defaults.lyx as a default template if it exists.
193         if (templatename.empty())
194                 tname = libFileSearch("templates", "defaults.lyx");
195         else
196                 tname = makeAbsPath(templatename);
197
198         if (!tname.empty()) {
199                 if (!b->readFile(tname)) {
200                         docstring const file = makeDisplayPath(tname.absFilename(), 50);
201                         docstring const text  = bformat(
202                                 _("The specified document template\n%1$s\ncould not be read."),
203                                 file);
204                         Alert::error(_("Could not read template"), text);
205                         theBufferList().release(b);
206                         return 0;
207                 }
208         }
209
210         if (!isNamed) {
211                 b->setUnnamed();
212                 b->setFileName(filename);
213         }
214
215         b->setReadonly(false);
216         b->fully_loaded(true);
217         b->updateDocLang(b->params().language);
218
219         return b;
220 }
221
222
223 void bufferErrors(Buffer const & buf, TeXErrors const & terr,
224                                   ErrorList & errorList)
225 {
226         TeXErrors::Errors::const_iterator cit = terr.begin();
227         TeXErrors::Errors::const_iterator end = terr.end();
228
229         for (; cit != end; ++cit) {
230                 int id_start = -1;
231                 int pos_start = -1;
232                 int errorrow = cit->error_in_line;
233                 bool found = buf.texrow().getIdFromRow(errorrow, id_start,
234                                                        pos_start);
235                 int id_end = -1;
236                 int pos_end = -1;
237                 do {
238                         ++errorrow;
239                         found = buf.texrow().getIdFromRow(errorrow, id_end,
240                                                           pos_end);
241                 } while (found && id_start == id_end && pos_start == pos_end);
242
243                 errorList.push_back(ErrorItem(cit->error_desc,
244                         cit->error_text, id_start, pos_start, pos_end));
245         }
246 }
247
248
249 string const bufferFormat(Buffer const & buffer)
250 {
251         if (buffer.isDocBook())
252                 return "docbook";
253         else if (buffer.isLiterate())
254                 return "literate";
255         else
256                 return "latex";
257 }
258
259
260 int countWords(DocIterator const & from, DocIterator const & to)
261 {
262         int count = 0;
263         bool inword = false;
264         for (DocIterator dit = from ; dit != to ; dit.forwardPos()) {
265                 // Copied and adapted from isLetter() in ControlSpellChecker
266                 if (dit.inTexted()
267                     && dit.pos() != dit.lastpos()
268                     && dit.paragraph().isLetter(dit.pos())
269                     && !dit.paragraph().isDeleted(dit.pos())) {
270                         if (!inword) {
271                                 ++count;
272                                 inword = true;
273                         }
274                 } else if (inword)
275                         inword = false;
276         }
277
278         return count;
279 }
280
281
282 namespace {
283
284 depth_type getDepth(DocIterator const & it)
285 {
286         depth_type depth = 0;
287         for (size_t i = 0 ; i < it.depth() ; ++i)
288                 if (!it[i].inset().inMathed())
289                         depth += it[i].paragraph().getDepth() + 1;
290         // remove 1 since the outer inset does not count
291         return depth - 1;
292 }
293
294 depth_type getItemDepth(ParIterator const & it)
295 {
296         Paragraph const & par = *it;
297         LYX_LABEL_TYPES const labeltype = par.layout()->labeltype;
298
299         if (labeltype != LABEL_ENUMERATE && labeltype != LABEL_ITEMIZE)
300                 return 0;
301
302         // this will hold the lowest depth encountered up to now.
303         depth_type min_depth = getDepth(it);
304         ParIterator prev_it = it;
305         while (true) {
306                 if (prev_it.pit())
307                         --prev_it.top().pit();
308                 else {
309                         // start of nested inset: go to outer par
310                         prev_it.pop_back();
311                         if (prev_it.empty()) {
312                                 // start of document: nothing to do
313                                 return 0;
314                         }
315                 }
316
317                 // We search for the first paragraph with same label
318                 // that is not more deeply nested.
319                 Paragraph & prev_par = *prev_it;
320                 depth_type const prev_depth = getDepth(prev_it);
321                 if (labeltype == prev_par.layout()->labeltype) {
322                         if (prev_depth < min_depth) {
323                                 return prev_par.itemdepth + 1;
324                         }
325                         else if (prev_depth == min_depth) {
326                                 return prev_par.itemdepth;
327                         }
328                 }
329                 min_depth = std::min(min_depth, prev_depth);
330                 // small optimization: if we are at depth 0, we won't
331                 // find anything else
332                 if (prev_depth == 0) {
333                         return 0;
334                 }
335         }
336 }
337
338
339 bool needEnumCounterReset(ParIterator const & it)
340 {
341         Paragraph const & par = *it;
342         BOOST_ASSERT(par.layout()->labeltype == LABEL_ENUMERATE);
343         depth_type const cur_depth = par.getDepth();
344         ParIterator prev_it = it;
345         while (prev_it.pit()) {
346                 --prev_it.top().pit();
347                 Paragraph const & prev_par = *prev_it;
348                 if (prev_par.getDepth() <= cur_depth)
349                         return  prev_par.layout()->labeltype != LABEL_ENUMERATE;
350         }
351         // start of nested inset: reset
352         return true;
353 }
354
355
356 void setCaptionLabels(InsetBase & inset, string const & type,
357                 docstring const label, Counters & counters)
358 {
359         LyXText * text = inset.getText(0);
360         if (!text)
361                 return;
362
363         ParagraphList & pars = text->paragraphs();
364         if (pars.empty())
365                 return;
366
367         docstring const counter = from_ascii(type);
368
369         ParagraphList::iterator p = pars.begin();
370         for (; p != pars.end(); ++p) {
371                 InsetList::iterator it2 = p->insetlist.begin();
372                 InsetList::iterator end2 = p->insetlist.end();
373                 // Any caption within this float should have the same
374                 // label prefix but different numbers.
375                 for (; it2 != end2; ++it2) {
376                         InsetBase & icap = *it2->inset;
377                         // Look deeper just in case.
378                         setCaptionLabels(icap, type, label, counters);
379                         if (icap.lyxCode() == InsetBase::CAPTION_CODE) {
380                                 // We found a caption!
381                                 counters.step(counter); 
382                                 int number = counters.value(counter);
383                                 InsetCaption & ic = static_cast<InsetCaption &>(icap);
384                                 ic.setType(type);
385                                 ic.setCount(number);
386                                 ic.setCustomLabel(label);
387                         }
388                 }
389         }
390 }
391
392
393 void setCaptions(Paragraph & par, LyXTextClass const & textclass)
394 {
395         if (par.insetlist.empty())
396                 return;
397
398         Counters & counters = textclass.counters();
399
400         InsetList::iterator it = par.insetlist.begin();
401         InsetList::iterator end = par.insetlist.end();
402         for (; it != end; ++it) {
403                 InsetBase & inset = *it->inset;
404                 if (inset.lyxCode() == InsetBase::FLOAT_CODE 
405                         || inset.lyxCode() == InsetBase::WRAP_CODE) {
406                         docstring const & name = inset.getInsetName();
407                         if (name.empty())
408                                 continue;
409
410                         Floating const & fl = textclass.floats().getType(to_ascii(name));
411                         // FIXME UNICODE
412                         string const & type = fl.type();
413                         docstring const label = from_utf8(fl.name());
414                         setCaptionLabels(inset, type, label, counters);
415                 }
416                 else if (inset.lyxCode() == InsetBase::TABULAR_CODE
417                         &&  static_cast<InsetTabular &>(inset).tabular.isLongTabular()) {
418                         // FIXME: are "table" and "Table" the correct type and label?
419                         setCaptionLabels(inset, "table", from_ascii("Table"), counters);
420                 }
421         }
422 }
423
424 // set the label of a paragraph. This includes the counters.
425 void setLabel(Buffer const & buf, ParIterator & it, LyXTextClass const & textclass)
426 {
427         Paragraph & par = *it;
428         LyXLayout_ptr const & layout = par.layout();
429         Counters & counters = textclass.counters();
430
431         if (it.pit() == 0) {
432                 par.params().appendix(par.params().startOfAppendix());
433         } else {
434                 par.params().appendix(it.plist()[it.pit() - 1].params().appendix());
435                 if (!par.params().appendix() &&
436                     par.params().startOfAppendix()) {
437                         par.params().appendix(true);
438                         textclass.counters().reset();
439                 }
440         }
441
442         // Compute the item depth of the paragraph
443         par.itemdepth = getItemDepth(it);
444
445         if (layout->margintype == MARGIN_MANUAL) {
446                 if (par.params().labelWidthString().empty())
447                         par.params().labelWidthString(par.translateIfPossible(layout->labelstring(), buf.params()));
448         } else {
449                 par.params().labelWidthString(docstring());
450         }
451
452         // Optimisation: setLabel() can be called for each for each
453         // paragraph of the document. So we make the string static to
454         // avoid the repeated instanciation.
455         static docstring itemlabel;
456
457         // is it a layout that has an automatic label?
458         if (layout->labeltype == LABEL_COUNTER) {
459                 if (layout->toclevel <= buf.params().secnumdepth
460                     && (layout->latextype != LATEX_ENVIRONMENT
461                         || isFirstInSequence(it.pit(), it.plist()))) {
462                         counters.step(layout->counter);
463                         par.params().labelString(
464                                 par.expandLabel(layout, buf.params()));
465                 } else
466                         par.params().labelString(docstring());
467
468         } else if (layout->labeltype == LABEL_ITEMIZE) {
469                 // At some point of time we should do something more
470                 // clever here, like:
471                 //   par.params().labelString(
472                 //    buf.params().user_defined_bullet(par.itemdepth).getText());
473                 // for now, use a simple hardcoded label
474                 switch (par.itemdepth) {
475                 case 0:
476                         itemlabel = char_type(0x2022);
477                         break;
478                 case 1:
479                         itemlabel = char_type(0x2013);
480                         break;
481                 case 2:
482                         itemlabel = char_type(0x2217);
483                         break;
484                 case 3:
485                         itemlabel = char_type(0x2219); // or 0x00b7
486                         break;
487                 }
488                 par.params().labelString(itemlabel);
489
490         } else if (layout->labeltype == LABEL_ENUMERATE) {
491                 // FIXME
492                 // Yes I know this is a really, really! bad solution
493                 // (Lgb)
494                 docstring enumcounter = from_ascii("enum");
495
496                 switch (par.itemdepth) {
497                 case 2:
498                         enumcounter += 'i';
499                 case 1:
500                         enumcounter += 'i';
501                 case 0:
502                         enumcounter += 'i';
503                         break;
504                 case 3:
505                         enumcounter += "iv";
506                         break;
507                 default:
508                         // not a valid enumdepth...
509                         break;
510                 }
511
512                 // Maybe we have to reset the enumeration counter.
513                 if (needEnumCounterReset(it))
514                         counters.reset(enumcounter);
515
516                 counters.step(enumcounter);
517
518                 string format;
519
520                 switch (par.itemdepth) {
521                 case 0:
522                         format = N_("\\arabic{enumi}.");
523                         break;
524                 case 1:
525                         format = N_("(\\alph{enumii})");
526                         break;
527                 case 2:
528                         format = N_("\\roman{enumiii}.");
529                         break;
530                 case 3:
531                         format = N_("\\Alph{enumiv}.");
532                         break;
533                 default:
534                         // not a valid enumdepth...
535                         break;
536                 }
537
538                 par.params().labelString(counters.counterLabel(
539                         par.translateIfPossible(from_ascii(format), buf.params())));
540
541         } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
542                 counters.step(from_ascii("bibitem"));
543                 int number = counters.value(from_ascii("bibitem"));
544                 if (par.bibitem())
545                         par.bibitem()->setCounter(number);
546
547                 par.params().labelString(
548                         par.translateIfPossible(layout->labelstring(), buf.params()));
549                 // In biblio shouldn't be following counters but...
550         } else if (layout->labeltype == LABEL_SENSITIVE) {
551                 // Search for the first float or wrap inset in the iterator
552                 size_t i = it.depth();
553                 InsetBase * in;
554                 while (i > 0) {
555                         --i;
556                         in = &it[i].inset();
557                         if (in->lyxCode() == InsetBase::FLOAT_CODE
558                             || in->lyxCode() == InsetBase::WRAP_CODE)
559                                 break;
560                 }
561                 docstring const & type = in->getInsetName();
562
563                 if (!type.empty()) {
564                         Floating const & fl = textclass.floats().getType(to_ascii(type));
565                         // FIXME UNICODE
566                         counters.step(from_ascii(fl.type()));
567
568                         // Doesn't work... yet.
569                         par.params().labelString(par.translateIfPossible(
570                                 bformat(from_ascii("%1$s #:"), from_utf8(fl.name())),
571                                 buf.params()));
572                 } else {
573                         // par->SetLayout(0);
574                         par.params().labelString(par.translateIfPossible(
575                                 layout->labelstring(), buf.params()));
576                 }
577
578         } else if (layout->labeltype == LABEL_NO_LABEL)
579                 par.params().labelString(docstring());
580         else
581                 par.params().labelString(
582                         par.translateIfPossible(layout->labelstring(), buf.params()));
583 }
584
585 } // anon namespace
586
587
588 bool updateCurrentLabel(Buffer const & buf,
589         ParIterator & it)
590 {
591     if (it == par_iterator_end(buf.inset()))
592         return false;
593
594 //      if (it.lastpit == 0 && LyXText::isMainText(buf))
595 //              return false;
596
597         switch (it->layout()->labeltype) {
598
599         case LABEL_NO_LABEL:
600         case LABEL_MANUAL:
601         case LABEL_BIBLIO:
602         case LABEL_TOP_ENVIRONMENT:
603         case LABEL_CENTERED_TOP_ENVIRONMENT:
604         case LABEL_STATIC:
605         case LABEL_ITEMIZE:
606                 setLabel(buf, it, buf.params().getLyXTextClass());
607                 return true;
608
609         case LABEL_SENSITIVE:
610         case LABEL_COUNTER:
611         // do more things with enumerate later
612         case LABEL_ENUMERATE:
613                 return false;
614         }
615
616         // This is dead code which get rid of a warning:
617         return true;
618 }
619
620
621 void updateLabels(Buffer const & buf,
622         ParIterator & from, ParIterator & to, bool childonly)
623 {
624         for (ParIterator it = from; it != to; ++it) {
625                 if (it.pit() > it.lastpit())
626                         return;
627                 if (!updateCurrentLabel (buf, it)) {
628                         updateLabels(buf, childonly);
629                         return;
630                 }
631         }
632 }
633
634
635 void updateLabels(Buffer const & buf, ParIterator & iter, bool childonly)
636 {
637         if (updateCurrentLabel(buf, iter))
638                 return;
639
640         updateLabels(buf, childonly);
641 }
642
643
644 void updateLabels(Buffer const & buf, bool childonly)
645 {
646         // Use the master text class also for child documents
647         LyXTextClass const & textclass = buf.params().getLyXTextClass();
648
649         if (!childonly) {
650                 // If this is a child document start with the master
651                 Buffer const * const master = buf.getMasterBuffer();
652                 if (master != &buf) {
653                         updateLabels(*master);
654                         return;
655                 }
656
657                 // start over the counters
658                 textclass.counters().reset();
659         }
660
661         ParIterator const end = par_iterator_end(buf.inset());
662
663         for (ParIterator it = par_iterator_begin(buf.inset()); it != end; ++it) {
664                 // reduce depth if necessary
665                 if (it.pit()) {
666                         Paragraph const & prevpar = it.plist()[it.pit() - 1];
667                         it->params().depth(min(it->params().depth(),
668                                                prevpar.getMaxDepthAfter()));
669                 } else
670                         it->params().depth(0);
671
672                 // set the counter for this paragraph
673                 setLabel(buf, it, textclass);
674
675                 // It is better to set the captions after setLabel because
676                 // the caption number might need the section number in the
677                 // future.
678                 setCaptions(*it, textclass);
679
680                 // Now included docs
681                 InsetList::const_iterator iit = it->insetlist.begin();
682                 InsetList::const_iterator end = it->insetlist.end();
683                 for (; iit != end; ++iit) {
684                         if (iit->inset->lyxCode() == InsetBase::INCLUDE_CODE)
685                                 static_cast<InsetInclude const *>(iit->inset)
686                                         ->updateLabels(buf);
687                 }
688         }
689
690         const_cast<Buffer &>(buf).tocBackend().update();
691 }
692
693
694 } // namespace lyx