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.
6 * \author Lars Gullik Bjønnes
7 * \author Alfredo Braunstein
9 * Full author contact details are available in file CREDITS.
15 #include "buffer_funcs.h"
17 #include "BufferList.h"
18 #include "BufferParams.h"
19 #include "DocIterator.h"
21 #include "ErrorList.h"
23 #include "FloatList.h"
24 #include "InsetList.h"
29 #include "TextClass.h"
30 #include "Paragraph.h"
31 #include "paragraph_funcs.h"
32 #include "ParagraphList.h"
33 #include "ParagraphParameters.h"
34 #include "ParIterator.h"
37 #include "TocBackend.h"
39 #include "frontends/alert.h"
41 #include "insets/InsetBibitem.h"
42 #include "insets/InsetInclude.h"
44 #include "support/convert.h"
45 #include "support/debug.h"
46 #include "support/filetools.h"
47 #include "support/gettext.h"
48 #include "support/lstrings.h"
49 #include "support/textutils.h"
52 using namespace lyx::support
;
56 namespace Alert
= frontend::Alert
;
59 Buffer
* checkAndLoadLyXFile(FileName
const & filename
)
62 Buffer
* checkBuffer
= theBufferList().getBuffer(filename
.absFilename());
64 if (checkBuffer
->isClean())
66 docstring
const file
= makeDisplayPath(filename
.absFilename(), 20);
67 docstring text
= bformat(_(
68 "The document %1$s is already loaded and has unsaved changes.\n"
69 "Do you want to abandon your changes and reload the version on disk?"), file
);
70 if (Alert::prompt(_("Reload saved document?"),
71 text
, 0, 1, _("&Reload"), _("&Keep Changes")))
74 // FIXME: should be LFUN_REVERT
75 theBufferList().release(checkBuffer
);
77 return checkAndLoadLyXFile(filename
);
80 if (filename
.exists()) {
81 if (!filename
.isReadableFile()) {
82 docstring text
= bformat(_("The file %1$s exists but is not "
83 "readable by the current user."),
84 from_utf8(filename
.absFilename()));
85 Alert::error(_("File not readable!"), text
);
88 Buffer
* b
= theBufferList().newBuffer(filename
.absFilename());
90 // Buffer creation is not possible.
92 if (!b
->loadLyXFile(filename
)) {
93 theBufferList().release(b
);
99 docstring text
= bformat(_("The document %1$s does not yet "
100 "exist.\n\nDo you want to create a new document?"),
101 from_utf8(filename
.absFilename()));
102 if (!Alert::prompt(_("Create new document?"),
103 text
, 0, 1, _("&Create"), _("Cancel")))
104 return newFile(filename
.absFilename(), string(), true);
110 // FIXME newFile() should probably be a member method of Application...
111 Buffer
* newFile(string
const & filename
, string
const & templatename
,
115 Buffer
* b
= theBufferList().newBuffer(filename
);
117 // Buffer creation is not possible.
121 // use defaults.lyx as a default template if it exists.
122 if (templatename
.empty())
123 tname
= libFileSearch("templates", "defaults.lyx");
125 tname
= makeAbsPath(templatename
);
127 if (!tname
.empty()) {
128 if (!b
->readFile(tname
)) {
129 docstring
const file
= makeDisplayPath(tname
.absFilename(), 50);
130 docstring
const text
= bformat(
131 _("The specified document template\n%1$s\ncould not be read."),
133 Alert::error(_("Could not read template"), text
);
134 theBufferList().release(b
);
141 b
->setFileName(filename
);
144 b
->setReadonly(false);
145 b
->setFullyLoaded(true);
151 Buffer
* newUnnamedFile(string
const & templatename
, FileName
const & path
)
153 static int newfile_number
;
155 string document_path
= path
.absFilename();
156 string filename
= addName(document_path
,
157 "newfile" + convert
<string
>(++newfile_number
) + ".lyx");
158 while (theBufferList().exists(filename
)
159 || FileName(filename
).isReadableFile()) {
161 filename
= addName(document_path
,
162 "newfile" + convert
<string
>(newfile_number
) + ".lyx");
164 return newFile(filename
, templatename
, false);
168 int countWords(DocIterator
const & from
, DocIterator
const & to
)
172 for (DocIterator dit
= from
; dit
!= to
; dit
.forwardPos()) {
173 // Copied and adapted from isLetter() in ControlSpellChecker
175 && dit
.pos() != dit
.lastpos()
176 && dit
.paragraph().isLetter(dit
.pos())
177 && !dit
.paragraph().isDeleted(dit
.pos())) {
190 int countChars(DocIterator
const & from
, DocIterator
const & to
, bool with_blanks
)
194 for (DocIterator dit
= from
; dit
!= to
; dit
.forwardPos()) {
196 if (!dit
.inTexted()) continue;
197 Paragraph
const & par
= dit
.paragraph();
198 pos_type
const pos
= dit
.pos();
200 if (pos
!= dit
.lastpos() && !par
.isDeleted(pos
)) {
201 if (Inset
const * ins
= par
.getInset(pos
)) {
204 else if (with_blanks
&& ins
->isSpace())
207 char_type
const c
= par
.getChar(pos
);
208 if (isPrintableNonspace(c
))
210 else if (isSpace(c
) && with_blanks
)
216 return chars
+ blanks
;
222 depth_type
getDepth(DocIterator
const & it
)
224 depth_type depth
= 0;
225 for (size_t i
= 0 ; i
< it
.depth() ; ++i
)
226 if (!it
[i
].inset().inMathed())
227 depth
+= it
[i
].paragraph().getDepth() + 1;
228 // remove 1 since the outer inset does not count
232 depth_type
getItemDepth(ParIterator
const & it
)
234 Paragraph
const & par
= *it
;
235 LabelType
const labeltype
= par
.layout().labeltype
;
237 if (labeltype
!= LABEL_ENUMERATE
&& labeltype
!= LABEL_ITEMIZE
)
240 // this will hold the lowest depth encountered up to now.
241 depth_type min_depth
= getDepth(it
);
242 ParIterator prev_it
= it
;
245 --prev_it
.top().pit();
247 // start of nested inset: go to outer par
249 if (prev_it
.empty()) {
250 // start of document: nothing to do
255 // We search for the first paragraph with same label
256 // that is not more deeply nested.
257 Paragraph
& prev_par
= *prev_it
;
258 depth_type
const prev_depth
= getDepth(prev_it
);
259 if (labeltype
== prev_par
.layout().labeltype
) {
260 if (prev_depth
< min_depth
)
261 return prev_par
.itemdepth
+ 1;
262 if (prev_depth
== min_depth
)
263 return prev_par
.itemdepth
;
265 min_depth
= min(min_depth
, prev_depth
);
266 // small optimization: if we are at depth 0, we won't
267 // find anything else
274 bool needEnumCounterReset(ParIterator
const & it
)
276 Paragraph
const & par
= *it
;
277 BOOST_ASSERT(par
.layout().labeltype
== LABEL_ENUMERATE
);
278 depth_type
const cur_depth
= par
.getDepth();
279 ParIterator prev_it
= it
;
280 while (prev_it
.pit()) {
281 --prev_it
.top().pit();
282 Paragraph
const & prev_par
= *prev_it
;
283 if (prev_par
.getDepth() <= cur_depth
)
284 return prev_par
.layout().labeltype
!= LABEL_ENUMERATE
;
286 // start of nested inset: reset
291 // set the label of a paragraph. This includes the counters.
292 void setLabel(Buffer
const & buf
, ParIterator
& it
)
294 DocumentClass
const & textclass
= buf
.params().documentClass();
295 Paragraph
& par
= it
.paragraph();
296 Layout
const & layout
= par
.layout();
297 Counters
& counters
= textclass
.counters();
299 if (par
.params().startOfAppendix()) {
300 // FIXME: only the counter corresponding to toplevel
301 // sectionning should be reset
303 counters
.appendix(true);
305 par
.params().appendix(counters
.appendix());
307 // Compute the item depth of the paragraph
308 par
.itemdepth
= getItemDepth(it
);
310 if (layout
.margintype
== MARGIN_MANUAL
) {
311 if (par
.params().labelWidthString().empty())
312 par
.params().labelWidthString(par
.translateIfPossible(layout
.labelstring(), buf
.params()));
314 par
.params().labelWidthString(docstring());
317 switch(layout
.labeltype
) {
319 if (layout
.toclevel
<= buf
.params().secnumdepth
320 && (layout
.latextype
!= LATEX_ENVIRONMENT
321 || isFirstInSequence(it
.pit(), it
.plist()))) {
322 counters
.step(layout
.counter
);
323 par
.params().labelString(
324 par
.expandLabel(layout
, buf
.params()));
326 par
.params().labelString(docstring());
329 case LABEL_ITEMIZE
: {
330 // At some point of time we should do something more
331 // clever here, like:
332 // par.params().labelString(
333 // buf.params().user_defined_bullet(par.itemdepth).getText());
334 // for now, use a simple hardcoded label
336 switch (par
.itemdepth
) {
338 itemlabel
= char_type(0x2022);
341 itemlabel
= char_type(0x2013);
344 itemlabel
= char_type(0x2217);
347 itemlabel
= char_type(0x2219); // or 0x00b7
350 par
.params().labelString(itemlabel
);
354 case LABEL_ENUMERATE
: {
355 // FIXME: Yes I know this is a really, really! bad solution
357 docstring enumcounter
= from_ascii("enum");
359 switch (par
.itemdepth
) {
371 // not a valid enumdepth...
375 // Maybe we have to reset the enumeration counter.
376 if (needEnumCounterReset(it
))
377 counters
.reset(enumcounter
);
379 counters
.step(enumcounter
);
383 switch (par
.itemdepth
) {
385 format
= N_("\\arabic{enumi}.");
388 format
= N_("(\\alph{enumii})");
391 format
= N_("\\roman{enumiii}.");
394 format
= N_("\\Alph{enumiv}.");
397 // not a valid enumdepth...
401 par
.params().labelString(counters
.counterLabel(
402 par
.translateIfPossible(from_ascii(format
), buf
.params())));
407 case LABEL_SENSITIVE
: {
408 string
const & type
= counters
.current_float();
409 docstring full_label
;
411 full_label
= buf
.B_("Senseless!!! ");
413 docstring name
= buf
.B_(textclass
.floats().getType(type
).name());
414 if (counters
.hasCounter(from_utf8(type
))) {
415 counters
.step(from_utf8(type
));
416 full_label
= bformat(from_ascii("%1$s %2$s:"),
418 counters
.theCounter(from_utf8(type
)));
420 full_label
= bformat(from_ascii("%1$s #:"), name
);
422 par
.params().labelString(full_label
);
427 par
.params().labelString(docstring());
431 case LABEL_TOP_ENVIRONMENT
:
432 case LABEL_CENTERED_TOP_ENVIRONMENT
:
435 par
.params().labelString(
436 par
.translateIfPossible(layout
.labelstring(),
444 void updateLabels(Buffer
const & buf
, ParIterator
& parit
)
446 BOOST_ASSERT(parit
.pit() == 0);
448 // set the position of the text in the buffer to be able
449 // to resolve macros in it. This has nothing to do with
450 // labels, but by putting it here we avoid implementing
451 // a whole bunch of traversal routines just for this call.
452 parit
.text()->setMacrocontextPosition(parit
);
454 depth_type maxdepth
= 0;
455 pit_type
const lastpit
= parit
.lastpit();
456 for ( ; parit
.pit() <= lastpit
; ++parit
.pit()) {
457 // reduce depth if necessary
458 parit
->params().depth(min(parit
->params().depth(), maxdepth
));
459 maxdepth
= parit
->getMaxDepthAfter();
461 // set the counter for this paragraph
462 setLabel(buf
, parit
);
465 InsetList::const_iterator iit
= parit
->insetList().begin();
466 InsetList::const_iterator end
= parit
->insetList().end();
467 for (; iit
!= end
; ++iit
) {
468 parit
.pos() = iit
->pos
;
469 iit
->inset
->updateLabels(parit
);
475 // FIXME: buf should should be const because updateLabels() modifies
476 // the contents of the paragraphs.
477 void updateLabels(Buffer
const & buf
, bool childonly
)
479 Buffer
const * const master
= buf
.masterBuffer();
480 // Use the master text class also for child documents
481 DocumentClass
const & textclass
= master
->params().documentClass();
484 // If this is a child document start with the master
485 if (master
!= &buf
) {
486 updateLabels(*master
);
490 // start over the counters
491 textclass
.counters().reset();
492 buf
.clearReferenceCache();
496 Buffer
& cbuf
= const_cast<Buffer
&>(buf
);
498 if (buf
.text().empty()) {
499 // FIXME: we don't call continue with updateLabels()
500 // here because it crashes on newly created documents.
501 // But the TocBackend needs to be initialised
502 // nonetheless so we update the tocBackend manually.
503 cbuf
.tocBackend().update();
508 ParIterator parit
= par_iterator_begin(buf
.inset());
509 updateLabels(buf
, parit
);
511 cbuf
.tocBackend().update();
513 cbuf
.structureChanged();