mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-11-11 05:33:33 +00:00
00c37e4781
TODO 1: All occurrences of "LyXView::showErrorList()" in the "kernel" should be replaced by a boost signal emission (Buffer::errors()). This signal is already connected to this showErrorList() slot. TODO 2: The ErrorList mechanism is used wrongly in a number of place, most notably in "Converter.C". Instead of replacing the ErrorList in the "Buffer" class, the "Converter" class should maintain its own list instead and connect directly to the LyXView::showErrorList() slot. Buffer: * errorList_: new private member and associated access methods. * setErrorList(): new accessor method. * addError(): apend an error to the errorList_. * error(): deleted. * errors(): new boost signal, unused for now. Shall be used instead of LyXView::showErrorList(). LyXView: * getErrorList(), addError(), errorlist_, errorConnection_: deleted. * errorsConnection_: new boost connection for the Buffer::errors() signal. lyx_main.C: * LyX::exec2(): manually print all errors. BufferView.h: remove unneeded ErrorList forward declaration. BufferView::pimpl::menuInsertLyXFile(): delete Buffer::error() connection and add a FIXME comment text.C: Use Buffer::addError() instead of Buffer::error() signal emission. ControlErrorList.C: get the ErrorList from the Buffer instead of LyXView git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@14467 a592a061-630c-0410-9148-cb99ea01b6c8
616 lines
14 KiB
C
616 lines
14 KiB
C
/**
|
|
* \file buffer_funcs.C
|
|
* This file is part of LyX, the document processor.
|
|
* Licence details can be found in the file COPYING.
|
|
*
|
|
* \author Lars Gullik Bjønnes
|
|
* \author Alfredo Braunstein
|
|
*
|
|
* Full author contact details are available in file CREDITS.
|
|
*
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "buffer_funcs.h"
|
|
#include "buffer.h"
|
|
#include "bufferlist.h"
|
|
#include "bufferparams.h"
|
|
#include "dociterator.h"
|
|
#include "counters.h"
|
|
#include "errorlist.h"
|
|
#include "Floating.h"
|
|
#include "FloatList.h"
|
|
#include "gettext.h"
|
|
#include "language.h"
|
|
#include "LaTeX.h"
|
|
#include "lyxtextclass.h"
|
|
#include "paragraph.h"
|
|
#include "paragraph_funcs.h"
|
|
#include "ParagraphList.h"
|
|
#include "ParagraphParameters.h"
|
|
#include "pariterator.h"
|
|
#include "lyxvc.h"
|
|
#include "texrow.h"
|
|
#include "vc-backend.h"
|
|
#include "toc.h"
|
|
|
|
#include "frontends/Alert.h"
|
|
|
|
#include "insets/insetbibitem.h"
|
|
|
|
#include "support/filetools.h"
|
|
#include "support/fs_extras.h"
|
|
#include "support/lyxlib.h"
|
|
|
|
#include <iostream>
|
|
|
|
#include <boost/bind.hpp>
|
|
#include <boost/filesystem/operations.hpp>
|
|
|
|
using namespace std;
|
|
|
|
using lyx::pit_type;
|
|
using lyx::support::bformat;
|
|
using lyx::support::libFileSearch;
|
|
using lyx::support::makeDisplayPath;
|
|
using lyx::support::onlyFilename;
|
|
using lyx::support::onlyPath;
|
|
using lyx::support::unlink;
|
|
|
|
using std::min;
|
|
using std::string;
|
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
extern BufferList bufferlist;
|
|
|
|
namespace {
|
|
|
|
bool readFile(Buffer * const b, string const & s)
|
|
{
|
|
BOOST_ASSERT(b);
|
|
|
|
// File information about normal file
|
|
if (!fs::exists(s)) {
|
|
string const file = makeDisplayPath(s, 50);
|
|
string text = bformat(_("The specified document\n%1$s"
|
|
"\ncould not be read."), file);
|
|
Alert::error(_("Could not read document"), text);
|
|
return false;
|
|
}
|
|
|
|
// Check if emergency save file exists and is newer.
|
|
string const e = onlyPath(s) + onlyFilename(s) + ".emergency";
|
|
|
|
if (fs::exists(e) && fs::exists(s)
|
|
&& fs::last_write_time(e) > fs::last_write_time(s))
|
|
{
|
|
string const file = makeDisplayPath(s, 20);
|
|
string const text =
|
|
bformat(_("An emergency save of the document "
|
|
"%1$s exists.\n\n"
|
|
"Recover emergency save?"), file);
|
|
switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
|
|
_("&Recover"), _("&Load Original"),
|
|
_("&Cancel")))
|
|
{
|
|
case 0:
|
|
// the file is not saved if we load the emergency file.
|
|
b->markDirty();
|
|
return b->readFile(e);
|
|
case 1:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Now check if autosave file is newer.
|
|
string const a = onlyPath(s) + '#' + onlyFilename(s) + '#';
|
|
|
|
if (fs::exists(a) && fs::exists(s)
|
|
&& fs::last_write_time(a) > fs::last_write_time(s))
|
|
{
|
|
string const file = makeDisplayPath(s, 20);
|
|
string const text =
|
|
bformat(_("The backup of the document "
|
|
"%1$s is newer.\n\nLoad the "
|
|
"backup instead?"), file);
|
|
switch (Alert::prompt(_("Load backup?"), text, 0, 2,
|
|
_("&Load backup"), _("Load &original"),
|
|
_("&Cancel") ))
|
|
{
|
|
case 0:
|
|
// the file is not saved if we load the autosave file.
|
|
b->markDirty();
|
|
return b->readFile(a);
|
|
case 1:
|
|
// Here we delete the autosave
|
|
unlink(a);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
return b->readFile(s);
|
|
}
|
|
|
|
|
|
} // namespace anon
|
|
|
|
|
|
|
|
bool loadLyXFile(Buffer * b, string const & s)
|
|
{
|
|
BOOST_ASSERT(b);
|
|
|
|
if (fs::is_readable(s)) {
|
|
if (readFile(b, s)) {
|
|
b->lyxvc().file_found_hook(s);
|
|
if (!fs::is_writable(s))
|
|
b->setReadonly(true);
|
|
return true;
|
|
}
|
|
} else {
|
|
string const file = makeDisplayPath(s, 20);
|
|
// Here we probably should run
|
|
if (LyXVC::file_not_found_hook(s)) {
|
|
string const text =
|
|
bformat(_("Do you want to retrieve the document"
|
|
" %1$s from version control?"), file);
|
|
int const ret = Alert::prompt(_("Retrieve from version control?"),
|
|
text, 0, 1, _("&Retrieve"), _("&Cancel"));
|
|
|
|
if (ret == 0) {
|
|
// How can we know _how_ to do the checkout?
|
|
// With the current VC support it has to be,
|
|
// a RCS file since CVS do not have special ,v files.
|
|
RCS::retrieve(s);
|
|
return loadLyXFile(b, s);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
Buffer * newFile(string const & filename, string const & templatename,
|
|
bool const isNamed)
|
|
{
|
|
// get a free buffer
|
|
Buffer * b = bufferlist.newBuffer(filename);
|
|
BOOST_ASSERT(b);
|
|
|
|
string tname;
|
|
// use defaults.lyx as a default template if it exists.
|
|
if (templatename.empty())
|
|
tname = libFileSearch("templates", "defaults.lyx");
|
|
else
|
|
tname = templatename;
|
|
|
|
if (!tname.empty()) {
|
|
if (!b->readFile(tname)) {
|
|
string const file = makeDisplayPath(tname, 50);
|
|
string const text = bformat(_("The specified document template\n%1$s\ncould not be read."), file);
|
|
Alert::error(_("Could not read template"), text);
|
|
// no template, start with empty buffer
|
|
}
|
|
}
|
|
|
|
if (!isNamed) {
|
|
b->setUnnamed();
|
|
b->setFileName(filename);
|
|
}
|
|
|
|
b->setReadonly(false);
|
|
b->fully_loaded(true);
|
|
b->updateDocLang(b->params().language);
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
void bufferErrors(Buffer const & buf, TeXErrors const & terr)
|
|
{
|
|
TeXErrors::Errors::const_iterator cit = terr.begin();
|
|
TeXErrors::Errors::const_iterator end = terr.end();
|
|
|
|
for (; cit != end; ++cit) {
|
|
int id_start = -1;
|
|
int pos_start = -1;
|
|
int errorrow = cit->error_in_line;
|
|
bool found = buf.texrow().getIdFromRow(errorrow, id_start,
|
|
pos_start);
|
|
int id_end = -1;
|
|
int pos_end = -1;
|
|
do {
|
|
++errorrow;
|
|
found = buf.texrow().getIdFromRow(errorrow, id_end,
|
|
pos_end);
|
|
} while (found && id_start == id_end && pos_start == pos_end);
|
|
|
|
buf.addError(ErrorItem(cit->error_desc,
|
|
cit->error_text, id_start, pos_start, pos_end));
|
|
}
|
|
}
|
|
|
|
|
|
void bufferErrors(Buffer const & buf, ErrorList const & el)
|
|
{
|
|
buf.setErrorList(el);
|
|
}
|
|
|
|
|
|
string const bufferFormat(Buffer const & buffer)
|
|
{
|
|
if (buffer.isLinuxDoc())
|
|
return "linuxdoc";
|
|
else if (buffer.isDocBook())
|
|
return "docbook";
|
|
else if (buffer.isLiterate())
|
|
return "literate";
|
|
else
|
|
return "latex";
|
|
}
|
|
|
|
|
|
int countWords(DocIterator const & from, DocIterator const & to)
|
|
{
|
|
int count = 0;
|
|
bool inword = false;
|
|
for (DocIterator dit = from ; dit != to ; dit.forwardPos()) {
|
|
// Copied and adapted from isLetter() in ControlSpellChecker
|
|
if (dit.inTexted()
|
|
&& dit.pos() != dit.lastpos()
|
|
&& dit.paragraph().isLetter(dit.pos())
|
|
&& !isDeletedText(dit.paragraph(), dit.pos())) {
|
|
if (!inword) {
|
|
++count;
|
|
inword = true;
|
|
}
|
|
} else if (inword)
|
|
inword = false;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
lyx::depth_type getDepth(DocIterator const & it)
|
|
{
|
|
lyx::depth_type depth = 0;
|
|
for (size_t i = 0 ; i < it.depth() ; ++i)
|
|
if (!it[i].inset().inMathed())
|
|
depth += it[i].paragraph().getDepth() + 1;
|
|
// remove 1 since the outer inset does not count
|
|
return depth - 1;
|
|
}
|
|
|
|
lyx::depth_type getItemDepth(ParIterator const & it)
|
|
{
|
|
Paragraph const & par = *it;
|
|
LYX_LABEL_TYPES const labeltype = par.layout()->labeltype;
|
|
|
|
if (labeltype != LABEL_ENUMERATE && labeltype != LABEL_ITEMIZE)
|
|
return 0;
|
|
|
|
// this will hold the lowest depth encountered up to now.
|
|
lyx::depth_type min_depth = getDepth(it);
|
|
ParIterator prev_it = it;
|
|
while (true) {
|
|
if (prev_it.pit())
|
|
--prev_it.top().pit();
|
|
else {
|
|
// start of nested inset: go to outer par
|
|
prev_it.pop_back();
|
|
if (prev_it.empty()) {
|
|
// start of document: nothing to do
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// We search for the first paragraph with same label
|
|
// that is not more deeply nested.
|
|
Paragraph & prev_par = *prev_it;
|
|
lyx::depth_type const prev_depth = getDepth(prev_it);
|
|
if (labeltype == prev_par.layout()->labeltype) {
|
|
if (prev_depth < min_depth) {
|
|
return prev_par.itemdepth + 1;
|
|
}
|
|
else if (prev_depth == min_depth) {
|
|
return prev_par.itemdepth;
|
|
}
|
|
}
|
|
min_depth = std::min(min_depth, prev_depth);
|
|
// small optimization: if we are at depth 0, we won't
|
|
// find anything else
|
|
if (prev_depth == 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool needEnumCounterReset(ParIterator const & it)
|
|
{
|
|
Paragraph const & par = *it;
|
|
BOOST_ASSERT(par.layout()->labeltype == LABEL_ENUMERATE);
|
|
lyx::depth_type const cur_depth = par.getDepth();
|
|
ParIterator prev_it = it;
|
|
while (prev_it.pit()) {
|
|
--prev_it.top().pit();
|
|
Paragraph const & prev_par = *prev_it;
|
|
if (prev_par.getDepth() <= cur_depth)
|
|
return prev_par.layout()->labeltype != LABEL_ENUMERATE;
|
|
}
|
|
// start of nested inset: reset
|
|
return true;
|
|
}
|
|
|
|
|
|
// set the label of a paragraph. This includes the counters.
|
|
void setLabel(Buffer const & buf, ParIterator & it)
|
|
{
|
|
Paragraph & par = *it;
|
|
BufferParams const & bufparams = buf.params();
|
|
LyXTextClass const & textclass = bufparams.getLyXTextClass();
|
|
LyXLayout_ptr const & layout = par.layout();
|
|
Counters & counters = textclass.counters();
|
|
|
|
if (it.pit() == 0) {
|
|
par.params().appendix(par.params().startOfAppendix());
|
|
} else {
|
|
par.params().appendix(it.plist()[it.pit() - 1].params().appendix());
|
|
if (!par.params().appendix() &&
|
|
par.params().startOfAppendix()) {
|
|
par.params().appendix(true);
|
|
textclass.counters().reset();
|
|
}
|
|
}
|
|
|
|
// Compute the item depth of the paragraph
|
|
par.itemdepth = getItemDepth(it);
|
|
|
|
// erase what was there before
|
|
par.params().labelString(string());
|
|
|
|
if (layout->margintype == MARGIN_MANUAL) {
|
|
if (par.params().labelWidthString().empty())
|
|
par.setLabelWidthString(layout->labelstring());
|
|
} else {
|
|
par.setLabelWidthString(string());
|
|
}
|
|
|
|
// is it a layout that has an automatic label?
|
|
if (layout->labeltype == LABEL_COUNTER) {
|
|
if (layout->toclevel <= buf.params().secnumdepth
|
|
&& (layout->latextype != LATEX_ENVIRONMENT
|
|
|| isFirstInSequence(it.pit(), it.plist()))) {
|
|
counters.step(layout->counter);
|
|
string label = expandLabel(buf, layout,
|
|
par.params().appendix());
|
|
par.params().labelString(label);
|
|
}
|
|
} else if (layout->labeltype == LABEL_ITEMIZE) {
|
|
// At some point of time we should do something more
|
|
// clever here, like:
|
|
// par.params().labelString(
|
|
// bufparams.user_defined_bullet(par.itemdepth).getText());
|
|
// for now, use a simple hardcoded label
|
|
string itemlabel;
|
|
switch (par.itemdepth) {
|
|
case 0:
|
|
itemlabel = "*";
|
|
break;
|
|
case 1:
|
|
itemlabel = "-";
|
|
break;
|
|
case 2:
|
|
itemlabel = "@";
|
|
break;
|
|
case 3:
|
|
itemlabel = "·";
|
|
break;
|
|
}
|
|
|
|
par.params().labelString(itemlabel);
|
|
} else if (layout->labeltype == LABEL_ENUMERATE) {
|
|
// FIXME
|
|
// Yes I know this is a really, really! bad solution
|
|
// (Lgb)
|
|
string enumcounter = "enum";
|
|
|
|
switch (par.itemdepth) {
|
|
case 2:
|
|
enumcounter += 'i';
|
|
case 1:
|
|
enumcounter += 'i';
|
|
case 0:
|
|
enumcounter += 'i';
|
|
break;
|
|
case 3:
|
|
enumcounter += "iv";
|
|
break;
|
|
default:
|
|
// not a valid enumdepth...
|
|
break;
|
|
}
|
|
|
|
// Maybe we have to reset the enumeration counter.
|
|
if (needEnumCounterReset(it))
|
|
counters.reset(enumcounter);
|
|
|
|
counters.step(enumcounter);
|
|
|
|
string format;
|
|
|
|
switch (par.itemdepth) {
|
|
case 0:
|
|
format = N_("\\arabic{enumi}.");
|
|
break;
|
|
case 1:
|
|
format = N_("(\\alph{enumii})");
|
|
break;
|
|
case 2:
|
|
format = N_("\\roman{enumiii}.");
|
|
break;
|
|
case 3:
|
|
format = N_("\\Alph{enumiv}.");
|
|
break;
|
|
default:
|
|
// not a valid enumdepth...
|
|
break;
|
|
}
|
|
|
|
par.params().labelString(counters.counterLabel(buf.B_(format)));
|
|
} else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
|
|
counters.step("bibitem");
|
|
int number = counters.value("bibitem");
|
|
if (par.bibitem())
|
|
par.bibitem()->setCounter(number);
|
|
par.params().labelString(buf.B_(layout->labelstring()));
|
|
// In biblio should't be following counters but...
|
|
} else if (layout->labeltype == LABEL_SENSITIVE) {
|
|
// Search for the first float or wrap inset in the iterator
|
|
string type;
|
|
size_t i = it.depth();
|
|
while (i > 0) {
|
|
--i;
|
|
InsetBase * const in = &it[i].inset();
|
|
if (in->lyxCode() == InsetBase::FLOAT_CODE
|
|
|| in->lyxCode() == InsetBase::WRAP_CODE) {
|
|
type = in->getInsetName();
|
|
break;
|
|
}
|
|
}
|
|
|
|
string s;
|
|
if (!type.empty()) {
|
|
Floating const & fl = textclass.floats().getType(type);
|
|
|
|
counters.step(fl.type());
|
|
|
|
// Doesn't work... yet.
|
|
s = bformat(_("%1$s #:"), buf.B_(fl.name()));
|
|
} else {
|
|
// par->SetLayout(0);
|
|
s = buf.B_(layout->labelstring());
|
|
}
|
|
|
|
par.params().labelString(s);
|
|
} else if (layout->labeltype == LABEL_NO_LABEL)
|
|
par.params().labelString(string());
|
|
else
|
|
par.params().labelString(buf.B_(layout->labelstring()));
|
|
}
|
|
|
|
} // anon namespace
|
|
|
|
|
|
bool updateCurrentLabel(Buffer const & buf,
|
|
ParIterator & it)
|
|
{
|
|
if (it == par_iterator_end(buf.inset()))
|
|
return false;
|
|
|
|
// if (it.lastpit == 0 && LyXText::isMainText())
|
|
// return false;
|
|
|
|
switch (it->layout()->labeltype) {
|
|
|
|
case LABEL_NO_LABEL:
|
|
case LABEL_MANUAL:
|
|
case LABEL_BIBLIO:
|
|
case LABEL_TOP_ENVIRONMENT:
|
|
case LABEL_CENTERED_TOP_ENVIRONMENT:
|
|
case LABEL_STATIC:
|
|
case LABEL_ITEMIZE:
|
|
setLabel(buf, it);
|
|
return true;
|
|
|
|
case LABEL_SENSITIVE:
|
|
case LABEL_COUNTER:
|
|
// do more things with enumerate later
|
|
case LABEL_ENUMERATE:
|
|
return false;
|
|
}
|
|
|
|
// This is dead code which get rid of a warning:
|
|
return true;
|
|
}
|
|
|
|
|
|
void updateLabels(Buffer const & buf,
|
|
ParIterator & from, ParIterator & to)
|
|
{
|
|
for (ParIterator it = from; it != to; ++it) {
|
|
if (it.pit() > it.lastpit())
|
|
return;
|
|
if (!updateCurrentLabel (buf, it)) {
|
|
updateLabels(buf);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void updateLabels(Buffer const & buf,
|
|
ParIterator & iter)
|
|
{
|
|
if (updateCurrentLabel(buf, iter))
|
|
return;
|
|
|
|
updateLabels(buf);
|
|
}
|
|
|
|
|
|
void updateLabels(Buffer const & buf)
|
|
{
|
|
// start over the counters
|
|
buf.params().getLyXTextClass().counters().reset();
|
|
|
|
ParIterator const end = par_iterator_end(buf.inset());
|
|
|
|
for (ParIterator it = par_iterator_begin(buf.inset()); it != end; ++it) {
|
|
// reduce depth if necessary
|
|
if (it.pit()) {
|
|
Paragraph const & prevpar = it.plist()[it.pit() - 1];
|
|
it->params().depth(min(it->params().depth(),
|
|
prevpar.getMaxDepthAfter()));
|
|
} else
|
|
it->params().depth(0);
|
|
|
|
// set the counter for this paragraph
|
|
setLabel(buf, it);
|
|
}
|
|
|
|
lyx::toc::updateToc(buf);
|
|
}
|
|
|
|
|
|
string expandLabel(Buffer const & buf,
|
|
LyXLayout_ptr const & layout, bool appendix)
|
|
{
|
|
LyXTextClass const & tclass = buf.params().getLyXTextClass();
|
|
|
|
string fmt = buf.B_(appendix ? layout->labelstring_appendix()
|
|
: layout->labelstring());
|
|
|
|
// handle 'inherited level parts' in 'fmt',
|
|
// i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
|
|
size_t const i = fmt.find('@', 0);
|
|
if (i != string::npos) {
|
|
size_t const j = fmt.find('@', i + 1);
|
|
if (j != string::npos) {
|
|
string parent(fmt, i + 1, j - i - 1);
|
|
string label = expandLabel(buf, tclass[parent], appendix);
|
|
fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
|
|
}
|
|
}
|
|
|
|
return tclass.counters().counterLabel(fmt);
|
|
}
|