]> git.lyx.org Git - lyx.git/blob - src/buffer_funcs.cpp
d66040c5630ad9e77bf803708743219ef827ac42
[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 "debug.h"
20 #include "DocIterator.h"
21 #include "Counters.h"
22 #include "ErrorList.h"
23 #include "Floating.h"
24 #include "FloatList.h"
25 #include "gettext.h"
26 #include "InsetList.h"
27 #include "InsetIterator.h"
28 #include "Language.h"
29 #include "LaTeX.h"
30 #include "Layout.h"
31 #include "LyX.h"
32 #include "lyxlayout_ptr_fwd.h"
33 #include "TextClass.h"
34 #include "TextClassList.h"
35 #include "Paragraph.h"
36 #include "paragraph_funcs.h"
37 #include "ParagraphList.h"
38 #include "ParagraphParameters.h"
39 #include "ParIterator.h"
40 #include "TexRow.h"
41 #include "Text.h"
42 #include "TocBackend.h"
43
44 #include "frontends/alert.h"
45
46 #include "insets/InsetBibitem.h"
47 #include "insets/InsetInclude.h"
48
49 #include "support/filetools.h"
50 #include "support/fs_extras.h"
51 #include "support/lyxlib.h"
52
53 #include <boost/bind.hpp>
54
55 using std::min;
56 using std::string;
57
58
59 namespace lyx {
60
61 using namespace std;
62
63 using support::bformat;
64 using support::FileName;
65 using support::libFileSearch;
66 using support::makeAbsPath;
67 using support::makeDisplayPath;
68 using support::onlyFilename;
69 using support::onlyPath;
70 using support::unlink;
71
72 namespace Alert = frontend::Alert;
73
74
75 bool checkIfLoaded(FileName const & fn)
76 {
77         return theBufferList().getBuffer(fn.absFilename());
78 }
79
80
81 Buffer * checkAndLoadLyXFile(FileName const & filename)
82 {
83         // File already open?
84         Buffer * checkBuffer = theBufferList().getBuffer(filename.absFilename());
85         if (checkBuffer) {
86                 if (checkBuffer->isClean())
87                         return checkBuffer;
88                 docstring const file = makeDisplayPath(filename.absFilename(), 20);
89                 docstring text = bformat(_(
90                                 "The document %1$s is already loaded and has unsaved changes.\n"
91                                 "Do you want to abandon your changes and reload the version on disk?"), file);
92                 if (Alert::prompt(_("Reload saved document?"),
93                                 text, 0, 1,  _("&Reload"), _("&Keep Changes")))
94                         return checkBuffer;
95
96                 // FIXME: should be LFUN_REVERT
97                 if (theBufferList().close(checkBuffer, false))
98                         // Load it again.
99                         return checkAndLoadLyXFile(filename);
100                 else
101                         // The file could not be closed.
102                         return 0;
103         }
104
105         if (filename.isReadable()) {
106                 Buffer * b = theBufferList().newBuffer(filename.absFilename());
107                 if (!b->loadLyXFile(filename)) {
108                         theBufferList().release(b);
109                         return 0;
110                 }
111                 return b;
112         }
113
114         docstring text = bformat(_("The document %1$s does not yet "
115                 "exist.\n\nDo you want to create a new document?"),
116                 from_utf8(filename.absFilename()));
117         if (!Alert::prompt(_("Create new document?"),
118                         text, 0, 1, _("&Create"), _("Cancel")))
119                 return newFile(filename.absFilename(), string(), true);
120
121         return 0;
122 }
123
124
125 // FIXME newFile() should probably be a member method of Application...
126 Buffer * newFile(string const & filename, string const & templatename,
127                  bool const isNamed)
128 {
129         // get a free buffer
130         Buffer * b = theBufferList().newBuffer(filename);
131         BOOST_ASSERT(b);
132
133         FileName tname;
134         // use defaults.lyx as a default template if it exists.
135         if (templatename.empty())
136                 tname = libFileSearch("templates", "defaults.lyx");
137         else
138                 tname = makeAbsPath(templatename);
139
140         if (!tname.empty()) {
141                 if (!b->readFile(tname)) {
142                         docstring const file = makeDisplayPath(tname.absFilename(), 50);
143                         docstring const text  = bformat(
144                                 _("The specified document template\n%1$s\ncould not be read."),
145                                 file);
146                         Alert::error(_("Could not read template"), text);
147                         theBufferList().release(b);
148                         return 0;
149                 }
150         }
151
152         if (!isNamed) {
153                 b->setUnnamed();
154                 b->setFileName(filename);
155         }
156
157         b->setReadonly(false);
158         b->setFullyLoaded(true);
159
160         return b;
161 }
162
163
164 int countWords(DocIterator const & from, DocIterator const & to)
165 {
166         int count = 0;
167         bool inword = false;
168         for (DocIterator dit = from ; dit != to ; dit.forwardPos()) {
169                 // Copied and adapted from isLetter() in ControlSpellChecker
170                 if (dit.inTexted()
171                     && dit.pos() != dit.lastpos()
172                     && dit.paragraph().isLetter(dit.pos())
173                     && !dit.paragraph().isDeleted(dit.pos())) {
174                         if (!inword) {
175                                 ++count;
176                                 inword = true;
177                         }
178                 } else if (inword)
179                         inword = false;
180         }
181
182         return count;
183 }
184
185
186 namespace {
187
188 depth_type getDepth(DocIterator const & it)
189 {
190         depth_type depth = 0;
191         for (size_t i = 0 ; i < it.depth() ; ++i)
192                 if (!it[i].inset().inMathed())
193                         depth += it[i].paragraph().getDepth() + 1;
194         // remove 1 since the outer inset does not count
195         return depth - 1;
196 }
197
198 depth_type getItemDepth(ParIterator const & it)
199 {
200         Paragraph const & par = *it;
201         LabelType const labeltype = par.layout()->labeltype;
202
203         if (labeltype != LABEL_ENUMERATE && labeltype != LABEL_ITEMIZE)
204                 return 0;
205
206         // this will hold the lowest depth encountered up to now.
207         depth_type min_depth = getDepth(it);
208         ParIterator prev_it = it;
209         while (true) {
210                 if (prev_it.pit())
211                         --prev_it.top().pit();
212                 else {
213                         // start of nested inset: go to outer par
214                         prev_it.pop_back();
215                         if (prev_it.empty()) {
216                                 // start of document: nothing to do
217                                 return 0;
218                         }
219                 }
220
221                 // We search for the first paragraph with same label
222                 // that is not more deeply nested.
223                 Paragraph & prev_par = *prev_it;
224                 depth_type const prev_depth = getDepth(prev_it);
225                 if (labeltype == prev_par.layout()->labeltype) {
226                         if (prev_depth < min_depth) {
227                                 return prev_par.itemdepth + 1;
228                         }
229                         else if (prev_depth == min_depth) {
230                                 return prev_par.itemdepth;
231                         }
232                 }
233                 min_depth = std::min(min_depth, prev_depth);
234                 // small optimization: if we are at depth 0, we won't
235                 // find anything else
236                 if (prev_depth == 0) {
237                         return 0;
238                 }
239         }
240 }
241
242
243 bool needEnumCounterReset(ParIterator const & it)
244 {
245         Paragraph const & par = *it;
246         BOOST_ASSERT(par.layout()->labeltype == LABEL_ENUMERATE);
247         depth_type const cur_depth = par.getDepth();
248         ParIterator prev_it = it;
249         while (prev_it.pit()) {
250                 --prev_it.top().pit();
251                 Paragraph const & prev_par = *prev_it;
252                 if (prev_par.getDepth() <= cur_depth)
253                         return  prev_par.layout()->labeltype != LABEL_ENUMERATE;
254         }
255         // start of nested inset: reset
256         return true;
257 }
258
259
260 // set the label of a paragraph. This includes the counters.
261 void setLabel(Buffer const & buf, ParIterator & it)
262 {
263         TextClass const & textclass = buf.params().getTextClass();
264         Paragraph & par = it.paragraph();
265         LayoutPtr const & layout = par.layout();
266         Counters & counters = textclass.counters();
267
268         if (par.params().startOfAppendix()) {
269                 // FIXME: only the counter corresponding to toplevel
270                 // sectionning should be reset
271                 counters.reset();
272                 counters.appendix(true);
273         }
274         par.params().appendix(counters.appendix());
275
276         // Compute the item depth of the paragraph
277         par.itemdepth = getItemDepth(it);
278
279         if (layout->margintype == MARGIN_MANUAL) {
280                 if (par.params().labelWidthString().empty())
281                         par.params().labelWidthString(par.translateIfPossible(layout->labelstring(), buf.params()));
282         } else {
283                 par.params().labelWidthString(docstring());
284         }
285
286         switch(layout->labeltype) {
287         case LABEL_COUNTER:
288                 if (layout->toclevel <= buf.params().secnumdepth
289                     && (layout->latextype != LATEX_ENVIRONMENT
290                         || isFirstInSequence(it.pit(), it.plist()))) {
291                         counters.step(layout->counter);
292                         par.params().labelString(
293                                 par.expandLabel(layout, buf.params()));
294                 } else
295                         par.params().labelString(docstring());
296                 break;
297
298         case LABEL_ITEMIZE: {
299                 // At some point of time we should do something more
300                 // clever here, like:
301                 //   par.params().labelString(
302                 //    buf.params().user_defined_bullet(par.itemdepth).getText());
303                 // for now, use a simple hardcoded label
304                 docstring itemlabel;
305                 switch (par.itemdepth) {
306                 case 0:
307                         itemlabel = char_type(0x2022);
308                         break;
309                 case 1:
310                         itemlabel = char_type(0x2013);
311                         break;
312                 case 2:
313                         itemlabel = char_type(0x2217);
314                         break;
315                 case 3:
316                         itemlabel = char_type(0x2219); // or 0x00b7
317                         break;
318                 }
319                 par.params().labelString(itemlabel);
320                 break;
321         }
322
323         case LABEL_ENUMERATE: {
324                 // FIXME: Yes I know this is a really, really! bad solution
325                 // (Lgb)
326                 docstring enumcounter = from_ascii("enum");
327
328                 switch (par.itemdepth) {
329                 case 2:
330                         enumcounter += 'i';
331                 case 1:
332                         enumcounter += 'i';
333                 case 0:
334                         enumcounter += 'i';
335                         break;
336                 case 3:
337                         enumcounter += "iv";
338                         break;
339                 default:
340                         // not a valid enumdepth...
341                         break;
342                 }
343
344                 // Maybe we have to reset the enumeration counter.
345                 if (needEnumCounterReset(it))
346                         counters.reset(enumcounter);
347
348                 counters.step(enumcounter);
349
350                 string format;
351
352                 switch (par.itemdepth) {
353                 case 0:
354                         format = N_("\\arabic{enumi}.");
355                         break;
356                 case 1:
357                         format = N_("(\\alph{enumii})");
358                         break;
359                 case 2:
360                         format = N_("\\roman{enumiii}.");
361                         break;
362                 case 3:
363                         format = N_("\\Alph{enumiv}.");
364                         break;
365                 default:
366                         // not a valid enumdepth...
367                         break;
368                 }
369
370                 par.params().labelString(counters.counterLabel(
371                         par.translateIfPossible(from_ascii(format), buf.params())));
372
373                 break;
374         }
375
376         case LABEL_SENSITIVE: {
377                 string const & type = counters.current_float();
378                 docstring full_label;
379                 if (type.empty())
380                         full_label = buf.B_("Senseless!!! ");
381                 else {
382                         docstring name = buf.B_(textclass.floats().getType(type).name());
383                         if (counters.hasCounter(from_utf8(type))) {
384                                 counters.step(from_utf8(type));
385                                 full_label = bformat(from_ascii("%1$s %2$s:"), 
386                                                      name, 
387                                                      counters.theCounter(from_utf8(type)));
388                         } else
389                                 full_label = bformat(from_ascii("%1$s #:"), name);      
390                 }
391                 par.params().labelString(full_label);   
392                 break;
393         }
394
395         case LABEL_NO_LABEL:
396                 par.params().labelString(docstring());
397                 break;
398
399         case LABEL_MANUAL:
400         case LABEL_TOP_ENVIRONMENT:
401         case LABEL_CENTERED_TOP_ENVIRONMENT:
402         case LABEL_STATIC:      
403         case LABEL_BIBLIO:
404                 par.params().labelString(
405                         par.translateIfPossible(layout->labelstring(), 
406                                                 buf.params()));
407                 break;
408         }
409 }
410
411 } // anon namespace
412
413 void updateLabels(Buffer const & buf, ParIterator & parit)
414 {
415         BOOST_ASSERT(parit.pit() == 0);
416
417         depth_type maxdepth = 0;
418         pit_type const lastpit = parit.lastpit();
419         for ( ; parit.pit() <= lastpit ; ++parit.pit()) {
420                 // reduce depth if necessary
421                 parit->params().depth(min(parit->params().depth(), maxdepth));
422                 maxdepth = parit->getMaxDepthAfter();
423
424                 // set the counter for this paragraph
425                 setLabel(buf, parit);
426
427                 // Now the insets
428                 InsetList::const_iterator iit = parit->insetList().begin();
429                 InsetList::const_iterator end = parit->insetList().end();
430                 for (; iit != end; ++iit) {
431                         parit.pos() = iit->pos;
432                         iit->inset->updateLabels(buf, parit);
433                 }
434         }
435         
436 }
437
438
439 // FIXME: buf should should be const because updateLabels() modifies
440 // the contents of the paragraphs.
441 void updateLabels(Buffer const & buf, bool childonly)
442 {
443         Buffer const * const master = buf.masterBuffer();
444         // Use the master text class also for child documents
445         TextClass const & textclass = master->params().getTextClass();
446
447         if (!childonly) {
448                 // If this is a child document start with the master
449                 if (master != &buf) {
450                         updateLabels(*master);
451                         return;
452                 }
453
454                 // start over the counters
455                 textclass.counters().reset();
456         }
457
458         Buffer & cbuf = const_cast<Buffer &>(buf);
459
460         if (buf.text().empty()) {
461                 // FIXME: we don't call continue with updateLabels()
462                 // here because it crashes on newly created documents.
463                 // But the TocBackend needs to be initialised
464                 // nonetheless so we update the tocBackend manually.
465                 cbuf.tocBackend().update();
466                 return;
467         }
468
469         // do the real work
470         ParIterator parit = par_iterator_begin(buf.inset());
471         updateLabels(buf, parit);
472
473         cbuf.tocBackend().update();
474         if (!childonly)
475                 cbuf.structureChanged();
476         // FIXME
477         // the embedding signal is emitted with structureChanged signal
478         // this is inaccurate so these two will be separated later.
479         //cbuf.embeddedFiles().update();
480         //cbuf.embeddingChanged();
481 }
482
483
484 void checkBufferStructure(Buffer & buffer, ParIterator const & par_it)
485 {
486         if (par_it->layout()->toclevel != Layout::NOT_IN_TOC) {
487                 Buffer * master = buffer.masterBuffer();
488                 master->tocBackend().updateItem(par_it);
489                 master->structureChanged();
490         }
491 }
492
493
494 } // namespace lyx