X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBuffer.cpp;h=8955a448dc722d3bb2ef0ddd9876bb58228ae2b6;hb=4291d4b0ff6d0a6eb9b92b855e0c44d94ac6ccca;hp=bdf866fbe12d863ab711351cdfefe113a46e8490;hpb=2e5355e4c22e5650adbb508fac5fc635b12524fc;p=lyx.git diff --git a/src/Buffer.cpp b/src/Buffer.cpp index bdf866fbe1..8955a448dc 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -4,6 +4,7 @@ * Licence details can be found in the file COPYING. * * \author Lars Gullik Bjønnes + * \author Stefan Schimanski * * Full author contact details are available in file CREDITS. */ @@ -18,71 +19,85 @@ #include "buffer_funcs.h" #include "BufferList.h" #include "BufferParams.h" -#include "Counters.h" #include "Bullet.h" #include "Chktex.h" -#include "debug.h" +#include "Converter.h" +#include "Counters.h" #include "DocIterator.h" +#include "EmbeddedFiles.h" #include "Encoding.h" #include "ErrorList.h" #include "Exporter.h" #include "Format.h" #include "FuncRequest.h" -#include "gettext.h" #include "InsetIterator.h" +#include "InsetList.h" #include "Language.h" -#include "LaTeX.h" #include "LaTeXFeatures.h" -#include "LyXAction.h" +#include "LaTeX.h" +#include "Layout.h" #include "Lexer.h" -#include "Text.h" +#include "LyXAction.h" #include "LyX.h" #include "LyXRC.h" #include "LyXVC.h" -#include "Messages.h" -#include "output.h" #include "output_docbook.h" +#include "output.h" #include "output_latex.h" -#include "Paragraph.h" +#include "output_plaintext.h" #include "paragraph_funcs.h" +#include "Paragraph.h" #include "ParagraphParameters.h" #include "ParIterator.h" +#include "PDFOptions.h" +#include "Session.h" #include "sgml.h" #include "TexRow.h" -#include "TextClassList.h" #include "TexStream.h" +#include "TextClassList.h" +#include "Text.h" #include "TocBackend.h" #include "Undo.h" +#include "VCBackend.h" #include "version.h" -#include "EmbeddedFiles.h" #include "insets/InsetBibitem.h" #include "insets/InsetBibtex.h" #include "insets/InsetInclude.h" #include "insets/InsetText.h" -#include "mathed/MathMacroTemplate.h" #include "mathed/MacroTable.h" +#include "mathed/MathMacroTemplate.h" #include "mathed/MathSupport.h" #include "frontends/alert.h" +#include "frontends/Delegates.h" +#include "frontends/WorkAreaManager.h" +#include "frontends/FileDialog.h" #include "graphics/Previews.h" -#include "support/types.h" -#include "support/lyxalgo.h" +#include "support/convert.h" +#include "support/debug.h" +#include "support/FileFilterList.h" #include "support/filetools.h" -#include "support/fs_extras.h" +#include "support/ForkedCalls.h" +#include "support/gettext.h" #include "support/gzstream.h" +#include "support/lstrings.h" +#include "support/lyxalgo.h" #include "support/lyxlib.h" #include "support/os.h" #include "support/Path.h" #include "support/textutils.h" -#include "support/convert.h" +#include "support/types.h" + +#if !defined (HAVE_FORK) +# define fork() -1 +#endif #include -#include -#include +#include #include #include @@ -106,7 +121,6 @@ using std::vector; using std::string; using std::time_t; - namespace lyx { using support::addName; @@ -114,9 +128,7 @@ using support::bformat; using support::changeExtension; using support::cmd_ret; using support::createBufferTmpDir; -using support::destroyDir; using support::FileName; -using support::getFormatFromContents; using support::libFileSearch; using support::latex_path; using support::ltrim; @@ -133,16 +145,14 @@ using support::split; using support::subst; using support::tempName; using support::trim; -using support::sum; using support::suffixIs; namespace Alert = frontend::Alert; namespace os = support::os; -namespace fs = boost::filesystem; namespace { -int const LYX_FORMAT = 283; +int const LYX_FORMAT = 303; // Uwe: Serbocroatian } // namespace anon @@ -153,13 +163,12 @@ class Buffer::Impl { public: Impl(Buffer & parent, FileName const & file, bool readonly); - - limited_stack undostack; - limited_stack redostack; + BufferParams params; LyXVC lyxvc; string temppath; - TexRow texrow; + mutable TexRow texrow; + Buffer const * parent_buffer; /// need to regenerate .tex? DepClean dep_clean; @@ -189,10 +198,12 @@ public: InsetText inset; /// - MacroTable macros; + mutable TocBackend toc_backend; - /// - TocBackend toc_backend; + /// macro table + typedef std::map > PositionToMacroMap; + typedef std::map NameToPositionMacroMap; + NameToPositionMacroMap macros; /// Container for all sort of Buffer dependant errors. map errorLists; @@ -204,45 +215,57 @@ public: /// modified. (Used to properly enable 'File->Revert to saved', bug 4114). time_t timestamp_; unsigned long checksum_; + + /// + frontend::WorkAreaManager * wa_; + + /// + Undo undo_; }; Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_) - : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_), - filename(file), file_fully_loaded(false), inset(params), - toc_backend(&parent), embedded_files(&parent), timestamp_(0), checksum_(0) + : parent_buffer(0), lyx_clean(true), bak_clean(true), unnamed(false), + read_only(readonly_), filename(file), file_fully_loaded(false), + inset(params), toc_backend(&parent), embedded_files(&parent), + timestamp_(0), checksum_(0), wa_(0), undo_(parent) { inset.setAutoBreakRows(true); - lyxvc.buffer(&parent); + lyxvc.setBuffer(&parent); temppath = createBufferTmpDir(); - params.filepath = onlyPath(file.absFilename()); + // FIXME: And now do something if temppath == string(), because we // assume from now on that temppath points to a valid temp dir. // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html + + if (use_gui) + wa_ = new frontend::WorkAreaManager; } Buffer::Buffer(string const & file, bool readonly) - : pimpl_(new Impl(*this, FileName(file), readonly)) + : d(new Impl(*this, FileName(file), readonly)), gui_(0) { - LYXERR(Debug::INFO) << "Buffer::Buffer()" << endl; + LYXERR(Debug::INFO, "Buffer::Buffer()"); } Buffer::~Buffer() { - LYXERR(Debug::INFO) << "Buffer::~Buffer()" << endl; + LYXERR(Debug::INFO, "Buffer::~Buffer()"); // here the buffer should take care that it is // saved properly, before it goes into the void. - Buffer * master = getMasterBuffer(); + Buffer const * master = masterBuffer(); if (master != this && use_gui) // We are closing buf which was a child document so we // must update the labels and section numbering of its master // Buffer. updateLabels(*master); - if (!temppath().empty() && !destroyDir(FileName(temppath()))) { + resetChildDocuments(false); + + if (!temppath().empty() && !FileName(temppath()).destroyDirectory()) { Alert::warning(_("Could not remove temporary directory"), bformat(_("Could not remove the temporary directory %1$s"), from_utf8(temppath()))); @@ -251,55 +274,49 @@ Buffer::~Buffer() // Remove any previewed LaTeX snippets associated with this buffer. graphics::Previews::get().removeLoader(*this); - closing(this); -} - - -Text & Buffer::text() const -{ - return const_cast(pimpl_->inset.text_); -} - - -Inset & Buffer::inset() const -{ - return const_cast(pimpl_->inset); + if (d->wa_) { + d->wa_->closeAll(); + delete d->wa_; + } + delete d; } -limited_stack & Buffer::undostack() +void Buffer::changed() const { - return pimpl_->undostack; + if (d->wa_) + d->wa_->redrawAll(); } -limited_stack const & Buffer::undostack() const +frontend::WorkAreaManager & Buffer::workAreaManager() const { - return pimpl_->undostack; + BOOST_ASSERT(d->wa_); + return *d->wa_; } -limited_stack & Buffer::redostack() +Text & Buffer::text() const { - return pimpl_->redostack; + return const_cast(d->inset.text_); } -limited_stack const & Buffer::redostack() const +Inset & Buffer::inset() const { - return pimpl_->redostack; + return const_cast(d->inset); } BufferParams & Buffer::params() { - return pimpl_->params; + return d->params; } BufferParams const & Buffer::params() const { - return pimpl_->params; + return d->params; } @@ -317,71 +334,69 @@ ParagraphList const & Buffer::paragraphs() const LyXVC & Buffer::lyxvc() { - return pimpl_->lyxvc; + return d->lyxvc; } LyXVC const & Buffer::lyxvc() const { - return pimpl_->lyxvc; + return d->lyxvc; } string const & Buffer::temppath() const { - return pimpl_->temppath; -} - - -TexRow & Buffer::texrow() -{ - return pimpl_->texrow; + return d->temppath; } TexRow const & Buffer::texrow() const { - return pimpl_->texrow; + return d->texrow; } -TocBackend & Buffer::tocBackend() +TocBackend & Buffer::tocBackend() const { - return pimpl_->toc_backend; + return d->toc_backend; } -TocBackend const & Buffer::tocBackend() const +EmbeddedFiles & Buffer::embeddedFiles() { - return pimpl_->toc_backend; + return d->embedded_files; } -EmbeddedFiles & Buffer::embeddedFiles() +EmbeddedFiles const & Buffer::embeddedFiles() const { - return pimpl_->embedded_files; + return d->embedded_files; } -EmbeddedFiles const & Buffer::embeddedFiles() const +Undo & Buffer::undo() { - return pimpl_->embedded_files; + return d->undo_; } -string const Buffer::getLatexName(bool const no_path) const +string Buffer::latexName(bool const no_path) const { - string const name = changeExtension(makeLatexName(fileName()), ".tex"); - return no_path ? onlyFilename(name) : name; + FileName latex_name = makeLatexName(d->filename); + return no_path ? latex_name.onlyFileName() + : latex_name.absFilename(); } -pair const Buffer::getLogName() const +string Buffer::logName(LogType * type) const { - string const filename = getLatexName(false); + string const filename = latexName(false); - if (filename.empty()) - return make_pair(Buffer::latexlog, string()); + if (filename.empty()) { + if (type) + *type = latexlog; + return string(); + } string const path = temppath(); @@ -395,48 +410,37 @@ pair const Buffer::getLogName() const // If no Latex log or Build log is newer, show Build log - if (fs::exists(bname.toFilesystemEncoding()) && - (!fs::exists(fname.toFilesystemEncoding()) || - fs::last_write_time(fname.toFilesystemEncoding()) < fs::last_write_time(bname.toFilesystemEncoding()))) { - LYXERR(Debug::FILES) << "Log name calculated as: " << bname << endl; - return make_pair(Buffer::buildlog, bname.absFilename()); + if (bname.exists() && + (!fname.exists() || fname.lastModified() < bname.lastModified())) { + LYXERR(Debug::FILES, "Log name calculated as: " << bname); + if (type) + *type = buildlog; + return bname.absFilename(); } - LYXERR(Debug::FILES) << "Log name calculated as: " << fname << endl; - return make_pair(Buffer::latexlog, fname.absFilename()); + LYXERR(Debug::FILES, "Log name calculated as: " << fname); + if (type) + *type = latexlog; + return fname.absFilename(); } void Buffer::setReadonly(bool const flag) { - if (pimpl_->read_only != flag) { - pimpl_->read_only = flag; - readonly(flag); + if (d->read_only != flag) { + d->read_only = flag; + setReadOnly(flag); } } void Buffer::setFileName(string const & newfile) { - pimpl_->filename = makeAbsPath(newfile); - params().filepath = onlyPath(pimpl_->filename.absFilename()); - setReadonly(fs::is_readonly(pimpl_->filename.toFilesystemEncoding())); + d->filename = makeAbsPath(newfile); + setReadonly(d->filename.isReadOnly()); updateTitles(); } -// We'll remove this later. (Lgb) -namespace { - -void unknownClass(string const & unknown) -{ - Alert::warning(_("Unknown document class"), - bformat(_("Using the default document class, because the " - "class %1$s is unknown."), from_utf8(unknown))); -} - -} // anon - - int Buffer::readHeader(Lexer & lex) { int unknown_tokens = 0; @@ -459,13 +463,14 @@ int Buffer::readHeader(Lexer & lex) params().footskip.erase(); params().listings_params.clear(); params().clearLayoutModules(); + params().pdfoptions().clear(); for (int i = 0; i < 4; ++i) { params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i]; params().temp_bullet(i) = ITEMIZE_DEFAULTS[i]; } - ErrorList & errorList = pimpl_->errorLists["Parse"]; + ErrorList & errorList = d->errorLists["Parse"]; while (lex.isOK()) { lex.next(); @@ -483,13 +488,15 @@ int Buffer::readHeader(Lexer & lex) continue; } - LYXERR(Debug::PARSER) << "Handling document header token: `" - << token << '\'' << endl; + LYXERR(Debug::PARSER, "Handling document header token: `" + << token << '\''); - string unknown = params().readToken(lex, token); + string unknown = params().readToken(lex, token, d->filename.onlyPath()); if (!unknown.empty()) { if (unknown[0] != '\\' && token == "\\textclass") { - unknownClass(unknown); + Alert::warning(_("Unknown document class"), + bformat(_("Using the default document class, because the " + "class %1$s is unknown."), from_utf8(unknown))); } else { ++unknown_tokens; docstring const s = bformat(_("Unknown token: " @@ -516,7 +523,7 @@ int Buffer::readHeader(Lexer & lex) // Returns false if "\end_document" is not read (Asger) bool Buffer::readDocument(Lexer & lex) { - ErrorList & errorList = pimpl_->errorLists["Parse"]; + ErrorList & errorList = d->errorLists["Parse"]; errorList.clear(); lex.next(); @@ -531,8 +538,9 @@ bool Buffer::readDocument(Lexer & lex) BOOST_ASSERT(paragraphs().empty()); readHeader(lex); - if (!params().getTextClass().load(filePath())) { - string theclass = params().getTextClass().name(); + TextClass const & baseClass = textclasslist[params().getBaseClass()]; + if (!baseClass.load(filePath())) { + string theclass = baseClass.name(); Alert::error(_("Can't load document class"), bformat( _("Using the default document class, because the " "class %1$s could not be loaded."), from_utf8(theclass))); @@ -559,6 +567,7 @@ bool Buffer::readDocument(Lexer & lex) } } + // read main text bool const res = text().read(*this, lex, errorList); for_each(text().paragraphs().begin(), text().paragraphs().end(), @@ -654,33 +663,23 @@ bool Buffer::readFile(FileName const & filename) { FileName fname(filename); // Check if the file is compressed. - string format = getFormatFromContents(filename); + string format = filename.guessFormatFromContents(); if (format == "zip") { // decompress to a temp directory - LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl; + LYXERR(Debug::FILES, filename << " is in zip format. Unzip to " << temppath()); ::unzipToDir(filename.toFilesystemEncoding(), temppath()); // - FileName manifest(addName(temppath(), "manifest.txt")); - FileName lyxfile(addName(temppath(), - onlyFilename(filename.toFilesystemEncoding()))); + FileName lyxfile(addName(temppath(), "content.lyx")); // if both manifest.txt and file.lyx exist, this is am embedded file - if (fs::exists(manifest.toFilesystemEncoding()) && - fs::exists(lyxfile.toFilesystemEncoding())) { + if (lyxfile.exists()) { params().embedded = true; fname = lyxfile; - // read manifest file - ifstream is(manifest.toFilesystemEncoding().c_str()); - is >> pimpl_->embedded_files; - is.close(); - LYXERR(Debug::FILES) << filename << " is a embedded file. Its manifest is:\n" - << pimpl_->embedded_files; } } // The embedded lyx file can also be compressed, for backward compatibility - format = getFormatFromContents(fname); - if (format == "gzip" || format == "zip" || format == "compress") { + format = fname.guessFormatFromContents(); + if (format == "gzip" || format == "zip" || format == "compress") params().compressed = true; - } // remove dummy empty par paragraphs().clear(); @@ -693,15 +692,15 @@ bool Buffer::readFile(FileName const & filename) } -bool Buffer::fully_loaded() const +bool Buffer::isFullyLoaded() const { - return pimpl_->file_fully_loaded; + return d->file_fully_loaded; } -void Buffer::fully_loaded(bool const value) +void Buffer::setFullyLoaded(bool value) { - pimpl_->file_fully_loaded = value; + d->file_fully_loaded = value; } @@ -717,7 +716,7 @@ Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename, } lex.next(); - string const token(lex.getString()); + string const token = lex.getString(); if (!lex) { Alert::error(_("Document could not be read"), @@ -749,14 +748,14 @@ Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename, // save timestamp and checksum of the original disk file, making sure // to not overwrite them with those of the file created in the tempdir // when it has to be converted to the current format. - if (!pimpl_->checksum_) { + if (!d->checksum_) { // Save the timestamp and checksum of disk file. If filename is an - // emergency file, save the timestamp and sum of the original lyx file + // emergency file, save the timestamp and checksum of the original lyx file // because isExternallyModified will check for this file. (BUG4193) - string diskfile = filename.toFilesystemEncoding(); + string diskfile = filename.absFilename(); if (suffixIs(diskfile, ".emergency")) diskfile = diskfile.substr(0, diskfile.size() - 10); - saveCheckSum(diskfile); + saveCheckSum(FileName(diskfile)); } if (file_format != LYX_FORMAT) { @@ -793,9 +792,7 @@ Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename, << ' ' << quoteName(filename.toFilesystemEncoding()); string const command_str = command.str(); - LYXERR(Debug::INFO) << "Running '" - << command_str << '\'' - << endl; + LYXERR(Debug::INFO, "Running '" << command_str << '\''); cmd_ret const ret = runCommand(command_str); if (ret.first != 0) { @@ -820,11 +817,7 @@ Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename, from_utf8(filename.absFilename()))); } - //lyxerr << "removing " << MacroTable::localMacros().size() - // << " temporary macro entries" << endl; - //MacroTable::localMacros().clear(); - - pimpl_->file_fully_loaded = true; + d->file_fully_loaded = true; return success; } @@ -835,33 +828,34 @@ bool Buffer::save() const // We don't need autosaves in the immediate future. (Asger) resetAutosaveTimers(); - string const encodedFilename = pimpl_->filename.toFilesystemEncoding(); + string const encodedFilename = d->filename.toFilesystemEncoding(); FileName backupName; bool madeBackup = false; // make a backup if the file already exists - if (lyxrc.make_backup && fs::exists(encodedFilename)) { - backupName = FileName(fileName() + '~'); - if (!lyxrc.backupdir_path.empty()) + if (lyxrc.make_backup && fileName().exists()) { + backupName = FileName(absFileName() + '~'); + if (!lyxrc.backupdir_path.empty()) { + string const mangledName = + subst(subst(backupName.absFilename(), '/', '!'), ':', '!'); backupName = FileName(addName(lyxrc.backupdir_path, - subst(os::internal_path(backupName.absFilename()), '/', '!'))); - - try { - fs::copy_file(encodedFilename, backupName.toFilesystemEncoding(), false); + mangledName)); + } + if (fileName().copyTo(backupName, true)) { madeBackup = true; - } catch (fs::filesystem_error const & fe) { + } else { Alert::error(_("Backup failure"), bformat(_("Cannot create backup file %1$s.\n" "Please check whether the directory exists and is writeable."), from_utf8(backupName.absFilename()))); - LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl; + //LYXERR(Debug::DEBUG, "Fs error: " << fe.what()); } } // ask if the disk file has been externally modified (use checksum method) - if (fs::exists(encodedFilename) && isExternallyModified(checksum_method)) { - docstring const file = makeDisplayPath(fileName(), 20); + if (fileName().exists() && isExternallyModified(checksum_method)) { + docstring const file = makeDisplayPath(absFileName(), 20); docstring text = bformat(_("Document %1$s has been externally modified. Are you sure " "you want to overwrite this file?"), file); int const ret = Alert::prompt(_("Overwrite modified file?"), @@ -870,15 +864,15 @@ bool Buffer::save() const return false; } - if (writeFile(pimpl_->filename)) { + if (writeFile(d->filename)) { markClean(); - removeAutosaveFile(fileName()); - saveCheckSum(pimpl_->filename.toFilesystemEncoding()); + removeAutosaveFile(absFileName()); + saveCheckSum(d->filename); return true; } else { // Saving failed, so backup is not backup if (madeBackup) - rename(backupName, pimpl_->filename); + rename(backupName, d->filename); return false; } } @@ -886,7 +880,7 @@ bool Buffer::save() const bool Buffer::writeFile(FileName const & fname) const { - if (pimpl_->read_only && fname == pimpl_->filename) + if (d->read_only && fname == d->filename) return false; bool retval = false; @@ -894,8 +888,7 @@ bool Buffer::writeFile(FileName const & fname) const FileName content; if (params().embedded) // first write the .lyx file to the temporary directory - content = FileName(addName(temppath(), - onlyFilename(fname.toFilesystemEncoding()))); + content = FileName(addName(temppath(), "content.lyx")); else content = fname; @@ -915,9 +908,8 @@ bool Buffer::writeFile(FileName const & fname) const if (retval && params().embedded) { // write file.lyx and all the embedded files to the zip file fname - // if embedding is enabled, and there is any embedded file - pimpl_->embedded_files.update(); - return pimpl_->embedded_files.write(fname); + // if embedding is enabled + return d->embedded_files.writeFile(fname); } return retval; } @@ -944,7 +936,7 @@ bool Buffer::write(ostream & ofs) const AuthorList::Authors::const_iterator a_it = params().authors().begin(); AuthorList::Authors::const_iterator a_end = params().authors().end(); for (; a_it != a_end; ++a_it) - a_it->second.used(false); + a_it->second.setUsed(false); ParIterator const end = par_iterator_end(); ParIterator it = par_iterator_begin(); @@ -988,11 +980,10 @@ bool Buffer::write(ostream & ofs) const bool Buffer::makeLaTeXFile(FileName const & fname, string const & original_path, OutputParams const & runparams, - bool output_preamble, bool output_body) + bool output_preamble, bool output_body) const { string const encoding = runparams.encoding->iconvName(); - LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: " - << encoding << "..." << endl; + LYXERR(Debug::LATEX, "makeLaTeXFile encoding: " << encoding << "..."); odocfstream ofs(encoding); if (!openFileWrite(ofs, fname)) @@ -1002,7 +993,7 @@ bool Buffer::makeLaTeXFile(FileName const & fname, bool failed_export = false; try { - texrow().reset(); + d->texrow.reset(); writeLaTeXSource(ofs, original_path, runparams, output_preamble, output_body); } @@ -1040,15 +1031,15 @@ bool Buffer::makeLaTeXFile(FileName const & fname, void Buffer::writeLaTeXSource(odocstream & os, string const & original_path, OutputParams const & runparams_in, - bool const output_preamble, bool const output_body) + bool const output_preamble, bool const output_body) const { OutputParams runparams = runparams_in; // validate the buffer. - LYXERR(Debug::LATEX) << " Validating buffer..." << endl; + LYXERR(Debug::LATEX, " Validating buffer..."); LaTeXFeatures features(*this, params(), runparams); validate(features); - LYXERR(Debug::LATEX) << " Buffer validation done." << endl; + LYXERR(Debug::LATEX, " Buffer validation done."); // The starting paragraph of the coming rows is the // first paragraph of the document. (Asger) @@ -1057,10 +1048,10 @@ void Buffer::writeLaTeXSource(odocstream & os, "For more info, see http://www.lyx.org/.\n" "%% Do not edit unless you really know what " "you are doing.\n"; - texrow().newline(); - texrow().newline(); + d->texrow.newline(); + d->texrow.newline(); } - LYXERR(Debug::INFO) << "lyx document header finished" << endl; + LYXERR(Debug::INFO, "lyx document header finished"); // There are a few differences between nice LaTeX and usual files: // usual is \batchmode and has a // special input@path to allow the including of figures @@ -1076,7 +1067,7 @@ void Buffer::writeLaTeXSource(odocstream & os, // code for usual, NOT nice-latex-file os << "\\batchmode\n"; // changed // from \nonstopmode - texrow().newline(); + d->texrow.newline(); } if (!original_path.empty()) { // FIXME UNICODE @@ -1086,25 +1077,25 @@ void Buffer::writeLaTeXSource(odocstream & os, << "\\def\\input@path{{" << inputpath << "/}}\n" << "\\makeatother\n"; - texrow().newline(); - texrow().newline(); - texrow().newline(); + d->texrow.newline(); + d->texrow.newline(); + d->texrow.newline(); } // Write the preamble - runparams.use_babel = params().writeLaTeX(os, features, texrow()); + runparams.use_babel = params().writeLaTeX(os, features, d->texrow); if (!output_body) return; // make the body. os << "\\begin{document}\n"; - texrow().newline(); + d->texrow.newline(); } // output_preamble - texrow().start(paragraphs().begin()->id(), 0); + d->texrow.start(paragraphs().begin()->id(), 0); - LYXERR(Debug::INFO) << "preamble finished, now the body." << endl; + LYXERR(Debug::INFO, "preamble finished, now the body."); if (!lyxrc.language_auto_begin && !params().language->babel().empty()) { @@ -1113,7 +1104,7 @@ void Buffer::writeLaTeXSource(odocstream & os, "$$lang", params().language->babel())) << '\n'; - texrow().newline(); + d->texrow.newline(); } Encoding const & encoding = params().encoding(); @@ -1123,37 +1114,37 @@ void Buffer::writeLaTeXSource(odocstream & os, // the preamble if it is handled by CJK.sty. os << "\\begin{CJK}{" << from_ascii(encoding.latexName()) << "}{}\n"; - texrow().newline(); + d->texrow.newline(); } // if we are doing a real file with body, even if this is the // child of some other buffer, let's cut the link here. // This happens for example if only a child document is printed. - string save_parentname; + Buffer const * save_parent = 0; if (output_preamble) { - save_parentname = params().parentname; - params().parentname.erase(); + save_parent = d->parent_buffer; + d->parent_buffer = 0; } - loadChildDocuments(*this); + loadChildDocuments(); // the real stuff - latexParagraphs(*this, paragraphs(), os, texrow(), runparams); + latexParagraphs(*this, paragraphs(), os, d->texrow, runparams); // Restore the parenthood if needed if (output_preamble) - params().parentname = save_parentname; + d->parent_buffer = save_parent; // add this just in case after all the paragraphs os << endl; - texrow().newline(); + d->texrow.newline(); if (encoding.package() == Encoding::CJK) { // Close the open CJK environment. // latexParagraphs will have opened one even if the last text // was not CJK. os << "\\end{CJK}\n"; - texrow().newline(); + d->texrow.newline(); } if (!lyxrc.language_auto_end && @@ -1162,26 +1153,23 @@ void Buffer::writeLaTeXSource(odocstream & os, "$$lang", params().language->babel())) << '\n'; - texrow().newline(); + d->texrow.newline(); } if (output_preamble) { os << "\\end{document}\n"; - texrow().newline(); - - LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl; + d->texrow.newline(); + LYXERR(Debug::LATEX, "makeLaTeXFile...done"); } else { - LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made." - << endl; + LYXERR(Debug::LATEX, "LaTeXFile for inclusion made."); } runparams_in.encoding = runparams.encoding; // Just to be sure. (Asger) - texrow().newline(); + d->texrow.newline(); - LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl; - LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1 - << '.' << endl; + LYXERR(Debug::INFO, "Finished making LaTeX file."); + LYXERR(Debug::INFO, "Row count was " << d->texrow.rows() - 1 << '.'); } @@ -1205,9 +1193,9 @@ bool Buffer::isDocBook() const void Buffer::makeDocBookFile(FileName const & fname, OutputParams const & runparams, - bool const body_only) + bool const body_only) const { - LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl; + LYXERR(Debug::LATEX, "makeDocBookFile..."); //ofstream ofs; odocfstream ofs; @@ -1224,12 +1212,12 @@ void Buffer::makeDocBookFile(FileName const & fname, void Buffer::writeDocBookSource(odocstream & os, string const & fname, OutputParams const & runparams, - bool const only_body) + bool const only_body) const { LaTeXFeatures features(*this, params(), runparams); validate(features); - texrow().reset(); + d->texrow.reset(); TextClass const & tclass = params().getTextClass(); string const top_element = tclass.latexname(); @@ -1258,8 +1246,8 @@ void Buffer::writeDocBookSource(odocstream & os, string const & fname, preamble += "\n"; } - string const name = runparams.nice ? changeExtension(fileName(), ".sgml") - : fname; + string const name = runparams.nice + ? changeExtension(absFileName(), ".sgml") : fname; preamble += features.getIncludedFiles(name); preamble += features.getLyXSGMLEntities(); @@ -1288,7 +1276,7 @@ void Buffer::writeDocBookSource(odocstream & os, string const & fname, params().getTextClass().counters().reset(); - loadChildDocuments(*this); + loadChildDocuments(); sgml::openTag(os, top); os << '\n'; @@ -1301,14 +1289,14 @@ void Buffer::writeDocBookSource(odocstream & os, string const & fname, // Other flags: -wall -v0 -x int Buffer::runChktex() { - busy(true); + setBusy(true); // get LaTeX-Filename FileName const path(temppath()); - string const name = addName(path.absFilename(), getLatexName()); + string const name = addName(path.absFilename(), latexName()); string const org_path = filePath(); - support::Path p(path); // path to LaTeX file + support::PathChanger p(path); // path to LaTeX file message(_("Running chktex...")); // Generate the LaTeX file if neccessary @@ -1325,14 +1313,12 @@ int Buffer::runChktex() Alert::error(_("chktex failure"), _("Could not run chktex successfully.")); } else if (res > 0) { - ErrorList & errorList = pimpl_->errorLists["ChkTeX"]; - // Clear out old errors - errorList.clear(); - // Fill-in the error list with the TeX errors - bufferErrors(*this, terr, errorList); + ErrorList & errlist = d->errorLists["ChkTeX"]; + errlist.clear(); + bufferErrors(terr, errlist); } - busy(false); + setBusy(false); errors("ChkTeX"); @@ -1379,7 +1365,7 @@ void Buffer::validate(LaTeXFeatures & features) const if (params().use_esint == BufferParams::package_on) features.require("esint"); - loadChildDocuments(*this); + loadChildDocuments(); for_each(paragraphs().begin(), paragraphs().end(), boost::bind(&Paragraph::validate, _1, boost::ref(features))); @@ -1418,9 +1404,9 @@ void Buffer::getLabelList(vector & list) const { /// if this is a child document and the parent is already loaded /// Use the parent's list instead [ale990407] - Buffer const * tmp = getMasterBuffer(); + Buffer const * tmp = masterBuffer(); if (!tmp) { - lyxerr << "getMasterBuffer() failed!" << endl; + lyxerr << "masterBuffer() failed!" << endl; BOOST_ASSERT(tmp); } if (tmp != this) { @@ -1428,18 +1414,18 @@ void Buffer::getLabelList(vector & list) const return; } - loadChildDocuments(*this); + loadChildDocuments(); for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) it.nextInset()->getLabelList(*this, list); } -void Buffer::updateBibfilesCache() +void Buffer::updateBibfilesCache() const { // if this is a child document and the parent is already loaded // update the parent's cache instead - Buffer * tmp = getMasterBuffer(); + Buffer const * tmp = masterBuffer(); BOOST_ASSERT(tmp); if (tmp != this) { tmp->updateBibfilesCache(); @@ -1448,14 +1434,14 @@ void Buffer::updateBibfilesCache() bibfilesCache_.clear(); for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) { - if (it->lyxCode() == Inset::BIBTEX_CODE) { + if (it->lyxCode() == BIBTEX_CODE) { InsetBibtex const & inset = static_cast(*it); vector const bibfiles = inset.getFiles(*this); bibfilesCache_.insert(bibfilesCache_.end(), bibfiles.begin(), bibfiles.end()); - } else if (it->lyxCode() == Inset::INCLUDE_CODE) { + } else if (it->lyxCode() == INCLUDE_CODE) { InsetInclude & inset = static_cast(*it); inset.updateBibfilesCache(*this); @@ -1473,7 +1459,7 @@ vector const & Buffer::getBibfilesCache() const { // if this is a child document and the parent is already loaded // use the parent's cache instead - Buffer const * tmp = getMasterBuffer(); + Buffer const * tmp = masterBuffer(); BOOST_ASSERT(tmp); if (tmp != this) return tmp->getBibfilesCache(); @@ -1488,8 +1474,8 @@ vector const & Buffer::getBibfilesCache() const bool Buffer::isDepClean(string const & name) const { - DepClean::const_iterator const it = pimpl_->dep_clean.find(name); - if (it == pimpl_->dep_clean.end()) + DepClean::const_iterator const it = d->dep_clean.find(name); + if (it == d->dep_clean.end()) return true; return it->second; } @@ -1497,7 +1483,7 @@ bool Buffer::isDepClean(string const & name) const void Buffer::markDepClean(string const & name) { - pimpl_->dep_clean[name] = true; + d->dep_clean[name] = true; } @@ -1513,7 +1499,7 @@ bool Buffer::dispatch(FuncRequest const & func, bool * result) switch (func.action) { case LFUN_BUFFER_EXPORT: { - bool const tmp = Exporter::Export(this, to_utf8(func.argument()), false); + bool const tmp = doExport(to_utf8(func.argument()), false); if (result) *result = tmp; break; @@ -1598,7 +1584,7 @@ ParConstIterator Buffer::par_iterator_end() const } -Language const * Buffer::getLanguage() const +Language const * Buffer::language() const { return params().language; } @@ -1612,181 +1598,230 @@ docstring const Buffer::B_(string const & l10n) const bool Buffer::isClean() const { - return pimpl_->lyx_clean; + return d->lyx_clean; } bool Buffer::isBakClean() const { - return pimpl_->bak_clean; + return d->bak_clean; } bool Buffer::isExternallyModified(CheckMethod method) const { - BOOST_ASSERT(fs::exists(pimpl_->filename.toFilesystemEncoding())); + BOOST_ASSERT(d->filename.exists()); // if method == timestamp, check timestamp before checksum return (method == checksum_method - || pimpl_->timestamp_ != fs::last_write_time(pimpl_->filename.toFilesystemEncoding())) - && pimpl_->checksum_ != sum(pimpl_->filename); + || d->timestamp_ != d->filename.lastModified()) + && d->checksum_ != d->filename.checksum(); } -void Buffer::saveCheckSum(string const & file) const +void Buffer::saveCheckSum(FileName const & file) const { - if (fs::exists(file)) { - pimpl_->timestamp_ = fs::last_write_time(file); - pimpl_->checksum_ = sum(FileName(file)); + if (file.exists()) { + d->timestamp_ = file.lastModified(); + d->checksum_ = file.checksum(); } else { // in the case of save to a new file. - pimpl_->timestamp_ = 0; - pimpl_->checksum_ = 0; + d->timestamp_ = 0; + d->checksum_ = 0; } } void Buffer::markClean() const { - if (!pimpl_->lyx_clean) { - pimpl_->lyx_clean = true; + if (!d->lyx_clean) { + d->lyx_clean = true; updateTitles(); } // if the .lyx file has been saved, we don't need an // autosave - pimpl_->bak_clean = true; + d->bak_clean = true; } -void Buffer::markBakClean() +void Buffer::markBakClean() const { - pimpl_->bak_clean = true; + d->bak_clean = true; } void Buffer::setUnnamed(bool flag) { - pimpl_->unnamed = flag; + d->unnamed = flag; } bool Buffer::isUnnamed() const { - return pimpl_->unnamed; + return d->unnamed; } // FIXME: this function should be moved to buffer_pimpl.C void Buffer::markDirty() { - if (pimpl_->lyx_clean) { - pimpl_->lyx_clean = false; + if (d->lyx_clean) { + d->lyx_clean = false; updateTitles(); } - pimpl_->bak_clean = false; + d->bak_clean = false; - DepClean::iterator it = pimpl_->dep_clean.begin(); - DepClean::const_iterator const end = pimpl_->dep_clean.end(); + DepClean::iterator it = d->dep_clean.begin(); + DepClean::const_iterator const end = d->dep_clean.end(); for (; it != end; ++it) it->second = false; } -string const Buffer::fileName() const +FileName Buffer::fileName() const { - return pimpl_->filename.absFilename(); + return d->filename; } -string const & Buffer::filePath() const +string Buffer::absFileName() const { - return params().filepath; + return d->filename.absFilename(); +} + + +string Buffer::filePath() const +{ + return d->filename.onlyPath().absFilename(); } bool Buffer::isReadonly() const { - return pimpl_->read_only; + return d->read_only; } -void Buffer::setParentName(string const & name) +void Buffer::setParent(Buffer const * buffer) { - if (name == pimpl_->filename.absFilename()) - // Avoids recursive include. - params().parentname.clear(); - else - params().parentname = name; + // Avoids recursive include. + d->parent_buffer = buffer == this ? 0 : buffer; } -Buffer const * Buffer::getMasterBuffer() const +Buffer const * Buffer::parent() { - if (!params().parentname.empty() - && theBufferList().exists(params().parentname)) { - Buffer const * buf = theBufferList().getBuffer(params().parentname); - //We need to check if the parent is us... - //FIXME RECURSIVE INCLUDE - //This is not sufficient, since recursive includes could be downstream. - if (buf && buf != this) - return buf->getMasterBuffer(); - } + return d->parent_buffer; +} + - return this; +Buffer const * Buffer::masterBuffer() const +{ + if (!d->parent_buffer) + return this; + + return d->parent_buffer->masterBuffer(); } -Buffer * Buffer::getMasterBuffer() +bool Buffer::hasMacro(docstring const & name, Paragraph const & par) const { - if (!params().parentname.empty() - && theBufferList().exists(params().parentname)) { - Buffer * buf = theBufferList().getBuffer(params().parentname); - if (buf) - return buf->getMasterBuffer(); - } + Impl::PositionToMacroMap::iterator it; + it = d->macros[name].upper_bound(par.macrocontextPosition()); + if (it != d->macros[name].end()) + return true; + + // If there is a master buffer, query that + Buffer const * master = masterBuffer(); + if (master && master != this) + return master->hasMacro(name); - return this; + return MacroTable::globalMacros().has(name); } -MacroData const & Buffer::getMacro(docstring const & name) const +bool Buffer::hasMacro(docstring const & name) const { - return pimpl_->macros.get(name); + if( !d->macros[name].empty() ) + return true; + + // If there is a master buffer, query that + Buffer const * master = masterBuffer(); + if (master && master != this) + return master->hasMacro(name); + + return MacroTable::globalMacros().has(name); } -bool Buffer::hasMacro(docstring const & name) const +MacroData const & Buffer::getMacro(docstring const & name, + Paragraph const & par) const { - return pimpl_->macros.has(name); + Impl::PositionToMacroMap::iterator it; + it = d->macros[name].upper_bound(par.macrocontextPosition()); + if( it != d->macros[name].end() ) + return it->second; + + // If there is a master buffer, query that + Buffer const * master = masterBuffer(); + if (master && master != this) + return master->getMacro(name); + + return MacroTable::globalMacros().get(name); } -void Buffer::insertMacro(docstring const & name, MacroData const & data) +MacroData const & Buffer::getMacro(docstring const & name) const { - MacroTable::globalMacros().insert(name, data); - pimpl_->macros.insert(name, data); + Impl::PositionToMacroMap::iterator it; + it = d->macros[name].begin(); + if( it != d->macros[name].end() ) + return it->second; + + // If there is a master buffer, query that + Buffer const * master = masterBuffer(); + if (master && master != this) + return master->getMacro(name); + + return MacroTable::globalMacros().get(name); } -void Buffer::buildMacros() +void Buffer::updateMacros() { - // Start with global table. - pimpl_->macros = MacroTable::globalMacros(); + // start with empty table + d->macros = Impl::NameToPositionMacroMap(); - // Now add our own. - ParagraphList const & pars = text().paragraphs(); + // Iterate over buffer + ParagraphList & pars = text().paragraphs(); for (size_t i = 0, n = pars.size(); i != n; ++i) { + // set position again + pars[i].setMacrocontextPosition(i); + //lyxerr << "searching main par " << i // << " for macro definitions" << std::endl; - InsetList const & insets = pars[i].insetlist; + InsetList const & insets = pars[i].insetList(); InsetList::const_iterator it = insets.begin(); InsetList::const_iterator end = insets.end(); for ( ; it != end; ++it) { - //lyxerr << "found inset code " << it->inset->lyxCode() << std::endl; - if (it->inset->lyxCode() == Inset::MATHMACRO_CODE) { - MathMacroTemplate const & mac - = static_cast(*it->inset); - insertMacro(mac.name(), mac.asMacroData()); + if (it->inset->lyxCode() != MATHMACRO_CODE) + continue; + + // get macro data + MathMacroTemplate const & macroTemplate + = static_cast(*it->inset); + + // valid? + if (macroTemplate.validMacro()) { + MacroData macro = macroTemplate.asMacroData(); + + // redefinition? + // call hasMacro here instead of directly querying mc to + // also take the master document into consideration + macro.setRedefinition(hasMacro(macroTemplate.name())); + + // register macro (possibly overwrite the previous one of this paragraph) + d->macros[macroTemplate.name()][i] = macro; } } } @@ -1794,14 +1829,15 @@ void Buffer::buildMacros() void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to, - Inset::Code code) + InsetCode code) { //FIXME: This does not work for child documents yet. - BOOST_ASSERT(code == Inset::CITE_CODE || code == Inset::REF_CODE); + BOOST_ASSERT(code == CITE_CODE || code == REF_CODE); // Check if the label 'from' appears more than once vector labels; - if (code == Inset::CITE_CODE) { + string paramName; + if (code == CITE_CODE) { BiblioInfo keys; keys.fillWithBibKeys(this); BiblioInfo::const_iterator bit = keys.begin(); @@ -1810,8 +1846,11 @@ void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to, for (; bit != bend; ++bit) // FIXME UNICODE labels.push_back(bit->first); - } else + paramName = "key"; + } else { getLabelList(labels); + paramName = "reference"; + } if (std::count(labels.begin(), labels.end(), from) > 1) return; @@ -1819,7 +1858,9 @@ void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to, for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) { if (it->lyxCode() == code) { InsetCommand & inset = static_cast(*it); - inset.replaceContents(to_utf8(from), to_utf8(to)); + docstring const oldValue = inset.getParam(paramName); + if (oldValue == from) + inset.setParam(paramName, to); } } } @@ -1835,15 +1876,15 @@ void Buffer::getSourceCode(odocstream & os, pit_type par_begin, // No side effect of file copying and image conversion runparams.dryrun = true; - texrow().reset(); + d->texrow.reset(); if (full_source) { os << "% " << _("Preview source code") << "\n\n"; - texrow().newline(); - texrow().newline(); + d->texrow.newline(); + d->texrow.newline(); if (isLatex()) writeLaTeXSource(os, filePath(), runparams, true, true); else { - writeDocBookSource(os, fileName(), runparams, false); + writeDocBookSource(os, absFileName(), runparams, false); } } else { runparams.par_begin = par_begin; @@ -1858,11 +1899,11 @@ void Buffer::getSourceCode(odocstream & os, pit_type par_begin, convert(par_begin), convert(par_end - 1)) << "\n\n"; - texrow().newline(); - texrow().newline(); + d->texrow.newline(); + d->texrow.newline(); // output paragraphs if (isLatex()) { - latexParagraphs(*this, paragraphs(), os, texrow(), runparams); + latexParagraphs(*this, paragraphs(), os, d->texrow, runparams); } else { // DocBook docbookParagraphs(paragraphs(), *this, os, runparams); @@ -1871,21 +1912,628 @@ void Buffer::getSourceCode(odocstream & os, pit_type par_begin, } -ErrorList const & Buffer::errorList(string const & type) const +ErrorList & Buffer::errorList(string const & type) const { - static ErrorList const emptyErrorList; - std::map::const_iterator I = pimpl_->errorLists.find(type); - if (I == pimpl_->errorLists.end()) + static ErrorList emptyErrorList; + std::map::iterator I = d->errorLists.find(type); + if (I == d->errorLists.end()) return emptyErrorList; return I->second; } -ErrorList & Buffer::errorList(string const & type) +void Buffer::structureChanged() const +{ + if (gui_) + gui_->structureChanged(); +} + + +void Buffer::errors(std::string const & err) const +{ + if (gui_) + gui_->errors(err); +} + + +void Buffer::message(docstring const & msg) const +{ + if (gui_) + gui_->message(msg); +} + + +void Buffer::setBusy(bool on) const +{ + if (gui_) + gui_->setBusy(on); +} + + +void Buffer::setReadOnly(bool on) const +{ + if (d->wa_) + d->wa_->setReadOnly(on); +} + + +void Buffer::updateTitles() const +{ + if (d->wa_) + d->wa_->updateTitles(); +} + + +void Buffer::resetAutosaveTimers() const +{ + if (gui_) + gui_->resetAutosaveTimers(); +} + + +void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui) +{ + gui_ = gui; +} + + + +namespace { + +class AutoSaveBuffer : public support::ForkedProcess { +public: + /// + AutoSaveBuffer(Buffer const & buffer, FileName const & fname) + : buffer_(buffer), fname_(fname) {} + /// + virtual boost::shared_ptr clone() const + { + return boost::shared_ptr(new AutoSaveBuffer(*this)); + } + /// + int start() + { + command_ = to_utf8(bformat(_("Auto-saving %1$s"), + from_utf8(fname_.absFilename()))); + return run(DontWait); + } +private: + /// + virtual int generateChild(); + /// + Buffer const & buffer_; + FileName fname_; +}; + + +#if !defined (HAVE_FORK) +# define fork() -1 +#endif + +int AutoSaveBuffer::generateChild() +{ + // tmp_ret will be located (usually) in /tmp + // will that be a problem? + pid_t const pid = fork(); + // If you want to debug the autosave + // you should set pid to -1, and comment out the fork. + if (pid == 0 || pid == -1) { + // pid = -1 signifies that lyx was unable + // to fork. But we will do the save + // anyway. + bool failed = false; + + FileName const tmp_ret(tempName(FileName(), "lyxauto")); + if (!tmp_ret.empty()) { + buffer_.writeFile(tmp_ret); + // assume successful write of tmp_ret + if (!rename(tmp_ret, fname_)) { + failed = true; + // most likely couldn't move between + // filesystems unless write of tmp_ret + // failed so remove tmp file (if it + // exists) + tmp_ret.removeFile(); + } + } else { + failed = true; + } + + if (failed) { + // failed to write/rename tmp_ret so try writing direct + if (!buffer_.writeFile(fname_)) { + // It is dangerous to do this in the child, + // but safe in the parent, so... + if (pid == -1) // emit message signal. + buffer_.message(_("Autosave failed!")); + } + } + if (pid == 0) { // we are the child so... + _exit(0); + } + } + return pid; +} + +} // namespace anon + + +// Perfect target for a thread... +void Buffer::autoSave() const +{ + if (isBakClean() || isReadonly()) { + // We don't save now, but we'll try again later + resetAutosaveTimers(); + return; + } + + // emit message signal. + message(_("Autosaving current document...")); + + // create autosave filename + string fname = filePath(); + fname += '#'; + fname += onlyFilename(absFileName()); + fname += '#'; + + AutoSaveBuffer autosave(*this, FileName(fname)); + autosave.start(); + + markBakClean(); + resetAutosaveTimers(); +} + + +/** Write a buffer to a new file name and rename the buffer + according to the new file name. + + This function is e.g. used by menu callbacks and + LFUN_BUFFER_WRITE_AS. + + If 'newname' is empty (the default), the user is asked via a + dialog for the buffer's new name and location. + + If 'newname' is non-empty and has an absolute path, that is used. + Otherwise the base directory of the buffer is used as the base + for any relative path in 'newname'. +*/ + +bool Buffer::writeAs(string const & newname) +{ + string fname = absFileName(); + string const oldname = fname; + + if (newname.empty()) { /// No argument? Ask user through dialog + + // FIXME UNICODE + FileDialog dlg(_("Choose a filename to save document as"), + LFUN_BUFFER_WRITE_AS); + dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path)); + dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path)); + + if (!support::isLyXFilename(fname)) + fname += ".lyx"; + + support::FileFilterList const filter(_("LyX Documents (*.lyx)")); + + FileDialog::Result result = + dlg.save(from_utf8(onlyPath(fname)), + filter, + from_utf8(onlyFilename(fname))); + + if (result.first == FileDialog::Later) + return false; + + fname = to_utf8(result.second); + + if (fname.empty()) + return false; + + // Make sure the absolute filename ends with appropriate suffix + fname = makeAbsPath(fname).absFilename(); + if (!support::isLyXFilename(fname)) + fname += ".lyx"; + + } else + fname = makeAbsPath(newname, onlyPath(oldname)).absFilename(); + + if (FileName(fname).exists()) { + docstring const file = makeDisplayPath(fname, 30); + docstring text = bformat(_("The document %1$s already " + "exists.\n\nDo you want to " + "overwrite that document?"), + file); + int const ret = Alert::prompt(_("Overwrite document?"), + text, 0, 1, _("&Overwrite"), _("&Cancel")); + + if (ret == 1) + return false; + } + + // Ok, change the name of the buffer + setFileName(fname); + markDirty(); + bool unnamed = isUnnamed(); + setUnnamed(false); + saveCheckSum(FileName(fname)); + + if (!menuWrite()) { + setFileName(oldname); + setUnnamed(unnamed); + saveCheckSum(FileName(oldname)); + return false; + } + + removeAutosaveFile(oldname); + return true; +} + + +bool Buffer::menuWrite() +{ + if (save()) { + LyX::ref().session().lastFiles().add(FileName(absFileName())); + return true; + } + + // FIXME: we don't tell the user *WHY* the save failed !! + + docstring const file = makeDisplayPath(absFileName(), 30); + + docstring text = bformat(_("The document %1$s could not be saved.\n\n" + "Do you want to rename the document and " + "try again?"), file); + int const ret = Alert::prompt(_("Rename and save?"), + text, 0, 1, _("&Rename"), _("&Cancel")); + + if (ret != 0) + return false; + + return writeAs(); +} + + +void Buffer::resetChildDocuments(bool close_them) const +{ + for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) { + if (it->lyxCode() != INCLUDE_CODE) + continue; + InsetCommand const & inset = static_cast(*it); + InsetCommandParams const & ip = inset.params(); + + resetParentBuffer(this, ip, close_them); + } + + if (use_gui && masterBuffer() == this) + updateLabels(*this); +} + + +void Buffer::loadChildDocuments() const +{ + bool parse_error = false; + + for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) { + if (it->lyxCode() != INCLUDE_CODE) + continue; + InsetCommand const & inset = static_cast(*it); + InsetCommandParams const & ip = inset.params(); + Buffer * child = loadIfNeeded(*this, ip); + if (!child) + continue; + parse_error |= !child->errorList("Parse").empty(); + child->loadChildDocuments(); + } + + if (use_gui && masterBuffer() == this) + updateLabels(*this); +} + + +string Buffer::bufferFormat() const +{ + if (isDocBook()) + return "docbook"; + if (isLiterate()) + return "literate"; + return "latex"; +} + + +bool Buffer::doExport(string const & format, bool put_in_tempdir, + string & result_file) const +{ + string backend_format; + OutputParams runparams(¶ms().encoding()); + runparams.flavor = OutputParams::LATEX; + runparams.linelen = lyxrc.plaintext_linelen; + vector backs = backends(); + if (find(backs.begin(), backs.end(), format) == backs.end()) { + // Get shortest path to format + Graph::EdgePath path; + for (vector::const_iterator it = backs.begin(); + it != backs.end(); ++it) { + Graph::EdgePath p = theConverters().getPath(*it, format); + if (!p.empty() && (path.empty() || p.size() < path.size())) { + backend_format = *it; + path = p; + } + } + if (!path.empty()) + runparams.flavor = theConverters().getFlavor(path); + else { + Alert::error(_("Couldn't export file"), + bformat(_("No information for exporting the format %1$s."), + formats.prettyName(format))); + return false; + } + } else { + backend_format = format; + // FIXME: Don't hardcode format names here, but use a flag + if (backend_format == "pdflatex") + runparams.flavor = OutputParams::PDFLATEX; + } + + string filename = latexName(false); + filename = addName(temppath(), filename); + filename = changeExtension(filename, + formats.extension(backend_format)); + + // Plain text backend + if (backend_format == "text") + writePlaintextFile(*this, FileName(filename), runparams); + // no backend + else if (backend_format == "lyx") + writeFile(FileName(filename)); + // Docbook backend + else if (isDocBook()) { + runparams.nice = !put_in_tempdir; + makeDocBookFile(FileName(filename), runparams); + } + // LaTeX backend + else if (backend_format == format) { + runparams.nice = true; + if (!makeLaTeXFile(FileName(filename), string(), runparams)) + return false; + } else if (!lyxrc.tex_allows_spaces + && support::contains(filePath(), ' ')) { + Alert::error(_("File name error"), + _("The directory path to the document cannot contain spaces.")); + return false; + } else { + runparams.nice = false; + if (!makeLaTeXFile(FileName(filename), filePath(), runparams)) + return false; + } + + string const error_type = (format == "program") + ? "Build" : bufferFormat(); + string const ext = formats.extension(format); + FileName const tmp_result_file(changeExtension(filename, ext)); + bool const success = theConverters().convert(this, FileName(filename), + tmp_result_file, FileName(absFileName()), backend_format, format, + errorList(error_type)); + // Emit the signal to show the error list. + if (format != backend_format) + errors(error_type); + if (!success) + return false; + + if (put_in_tempdir) + result_file = tmp_result_file.absFilename(); + else { + result_file = changeExtension(absFileName(), ext); + // We need to copy referenced files (e. g. included graphics + // if format == "dvi") to the result dir. + vector const files = + runparams.exportdata->externalFiles(format); + string const dest = onlyPath(result_file); + CopyStatus status = SUCCESS; + for (vector::const_iterator it = files.begin(); + it != files.end() && status != CANCEL; ++it) { + string const fmt = + formats.getFormatFromFile(it->sourceName); + status = copyFile(fmt, it->sourceName, + makeAbsPath(it->exportName, dest), + it->exportName, status == FORCE); + } + if (status == CANCEL) { + message(_("Document export cancelled.")); + } else if (tmp_result_file.exists()) { + // Finally copy the main file + status = copyFile(format, tmp_result_file, + FileName(result_file), result_file, + status == FORCE); + message(bformat(_("Document exported as %1$s " + "to file `%2$s'"), + formats.prettyName(format), + makeDisplayPath(result_file))); + } else { + // This must be a dummy converter like fax (bug 1888) + message(bformat(_("Document exported as %1$s"), + formats.prettyName(format))); + } + } + + return true; +} + + +bool Buffer::doExport(string const & format, bool put_in_tempdir) const +{ + string result_file; + return doExport(format, put_in_tempdir, result_file); +} + + +bool Buffer::preview(string const & format) const +{ + string result_file; + if (!doExport(format, true, result_file)) + return false; + return formats.view(*this, FileName(result_file), format); +} + + +bool Buffer::isExportable(string const & format) const +{ + vector backs = backends(); + for (vector::const_iterator it = backs.begin(); + it != backs.end(); ++it) + if (theConverters().isReachable(*it, format)) + return true; + return false; +} + + +vector Buffer::exportableFormats(bool only_viewable) const { - return pimpl_->errorLists[type]; + vector backs = backends(); + vector result = + theConverters().getReachable(backs[0], only_viewable, true); + for (vector::const_iterator it = backs.begin() + 1; + it != backs.end(); ++it) { + vector r = + theConverters().getReachable(*it, only_viewable, false); + result.insert(result.end(), r.begin(), r.end()); + } + return result; +} + + +vector Buffer::backends() const +{ + vector v; + if (params().getTextClass().isTeXClassAvailable()) { + v.push_back(bufferFormat()); + // FIXME: Don't hardcode format names here, but use a flag + if (v.back() == "latex") + v.push_back("pdflatex"); + } + v.push_back("text"); + v.push_back("lyx"); + return v; } +bool Buffer::readFileHelper(FileName const & s) +{ + // File information about normal file + if (!s.exists()) { + docstring const file = makeDisplayPath(s.absFilename(), 50); + docstring 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. + FileName const e(s.absFilename() + ".emergency"); + + if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) { + docstring const file = makeDisplayPath(s.absFilename(), 20); + docstring 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. + markDirty(); + return readFile(e); + case 1: + break; + default: + return false; + } + } + + // Now check if autosave file is newer. + FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#'); + + if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) { + docstring const file = makeDisplayPath(s.absFilename(), 20); + docstring 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. + markDirty(); + return readFile(a); + case 1: + // Here we delete the autosave + a.removeFile(); + break; + default: + return false; + } + } + return readFile(s); +} + + +bool Buffer::loadLyXFile(FileName const & s) +{ + if (s.isReadableFile()) { + if (readFileHelper(s)) { + lyxvc().file_found_hook(s); + if (!s.isWritable()) + setReadonly(true); + return true; + } + } else { + docstring const file = makeDisplayPath(s.absFilename(), 20); + // Here we probably should run + if (LyXVC::file_not_found_hook(s)) { + docstring 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(s); + } + } + } + return false; +} + + +void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const +{ + 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 = d->texrow.getIdFromRow(errorRow, id_start, + pos_start); + int id_end = -1; + int pos_end = -1; + do { + ++errorRow; + found = d->texrow.getIdFromRow(errorRow, id_end, pos_end); + } while (found && id_start == id_end && pos_start == pos_end); + + errorList.push_back(ErrorItem(cit->error_desc, + cit->error_text, id_start, pos_start, pos_end)); + } +} + } // namespace lyx