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
8 * Full author contact details are available in file CREDITS.
16 #include "BiblioInfo.h"
17 #include "BranchList.h"
18 #include "buffer_funcs.h"
19 #include "BufferList.h"
20 #include "BufferParams.h"
23 #include "Converter.h"
26 #include "DocIterator.h"
27 #include "EmbeddedFiles.h"
29 #include "ErrorList.h"
32 #include "FuncRequest.h"
34 #include "InsetIterator.h"
35 #include "InsetList.h"
37 #include "LaTeXFeatures.h"
41 #include "LyXAction.h"
46 #include "output_docbook.h"
48 #include "output_latex.h"
49 #include "output_plaintext.h"
50 #include "paragraph_funcs.h"
51 #include "Paragraph.h"
52 #include "ParagraphParameters.h"
53 #include "ParIterator.h"
54 #include "PDFOptions.h"
58 #include "TexStream.h"
59 #include "TextClassList.h"
61 #include "TocBackend.h"
63 #include "VCBackend.h"
66 #include "insets/InsetBibitem.h"
67 #include "insets/InsetBibtex.h"
68 #include "insets/InsetInclude.h"
69 #include "insets/InsetText.h"
71 #include "mathed/MathMacroTemplate.h"
72 #include "mathed/MacroTable.h"
73 #include "mathed/MathSupport.h"
75 #include "frontends/alert.h"
76 #include "frontends/Delegates.h"
77 #include "frontends/WorkAreaManager.h"
78 #include "frontends/FileDialog.h"
80 #include "graphics/Previews.h"
82 #include "support/types.h"
83 #include "support/lyxalgo.h"
84 #include "support/FileFilterList.h"
85 #include "support/filetools.h"
86 #include "support/Forkedcall.h"
87 #include "support/fs_extras.h"
88 #include "support/gzstream.h"
89 #include "support/lyxlib.h"
90 #include "support/os.h"
91 #include "support/Path.h"
92 #include "support/textutils.h"
93 #include "support/convert.h"
95 #if !defined (HAVE_FORK)
99 #include <boost/bind.hpp>
100 #include <boost/filesystem/exception.hpp>
101 #include <boost/filesystem/operations.hpp>
102 #include <boost/shared_ptr.hpp>
112 using std::make_pair;
117 using std::ostringstream;
128 using support::addName;
129 using support::bformat;
130 using support::changeExtension;
131 using support::cmd_ret;
132 using support::createBufferTmpDir;
133 using support::FileName;
134 using support::libFileSearch;
135 using support::latex_path;
136 using support::ltrim;
137 using support::makeAbsPath;
138 using support::makeDisplayPath;
139 using support::makeLatexName;
140 using support::onlyFilename;
141 using support::onlyPath;
142 using support::quoteName;
143 using support::removeAutosaveFile;
144 using support::rename;
145 using support::runCommand;
146 using support::split;
147 using support::subst;
148 using support::tempName;
151 using support::suffixIs;
153 namespace Alert = frontend::Alert;
154 namespace os = support::os;
155 namespace fs = boost::filesystem;
159 int const LYX_FORMAT = 298; //Schimmi: Optional parameters in macros
164 typedef std::map<string, bool> DepClean;
169 Impl(Buffer & parent, FileName const & file, bool readonly);
176 /// need to regenerate .tex?
180 mutable bool lyx_clean;
182 /// is autosave needed?
183 mutable bool bak_clean;
185 /// is this a unnamed file (New...)?
191 /// name of the file the buffer is associated with.
194 /** Set to true only when the file is fully loaded.
195 * Used to prevent the premature generation of previews
196 * and by the citation inset.
198 bool file_fully_loaded;
200 /// our Text that should be wrapped in an InsetText
207 TocBackend toc_backend;
209 /// Container for all sort of Buffer dependant errors.
210 map<string, ErrorList> errorLists;
212 /// all embedded files of this buffer
213 EmbeddedFiles embedded_files;
215 /// timestamp and checksum used to test if the file has been externally
216 /// modified. (Used to properly enable 'File->Revert to saved', bug 4114).
218 unsigned long checksum_;
221 frontend::WorkAreaManager * wa_;
228 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
229 : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
230 filename(file), file_fully_loaded(false), inset(params),
231 toc_backend(&parent), embedded_files(&parent), timestamp_(0),
232 checksum_(0), wa_(0), undo_(parent)
234 inset.setAutoBreakRows(true);
235 lyxvc.setBuffer(&parent);
236 temppath = createBufferTmpDir();
237 params.filepath = onlyPath(file.absFilename());
238 // FIXME: And now do something if temppath == string(), because we
239 // assume from now on that temppath points to a valid temp dir.
240 // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html
243 wa_ = new frontend::WorkAreaManager;
247 Buffer::Buffer(string const & file, bool readonly)
248 : pimpl_(new Impl(*this, FileName(file), readonly)), gui_(0)
250 LYXERR(Debug::INFO) << "Buffer::Buffer()" << endl;
256 LYXERR(Debug::INFO) << "Buffer::~Buffer()" << endl;
257 // here the buffer should take care that it is
258 // saved properly, before it goes into the void.
260 Buffer * master = masterBuffer();
261 if (master != this && use_gui)
262 // We are closing buf which was a child document so we
263 // must update the labels and section numbering of its master
265 updateLabels(*master);
267 if (!temppath().empty() && !FileName(temppath()).destroyDirectory()) {
268 Alert::warning(_("Could not remove temporary directory"),
269 bformat(_("Could not remove the temporary directory %1$s"),
270 from_utf8(temppath())));
273 // Remove any previewed LaTeX snippets associated with this buffer.
274 graphics::Previews::get().removeLoader(*this);
277 pimpl_->wa_->closeAll();
284 void Buffer::changed() const
287 pimpl_->wa_->redrawAll();
291 frontend::WorkAreaManager & Buffer::workAreaManager() const
293 BOOST_ASSERT(pimpl_->wa_);
298 Text & Buffer::text() const
300 return const_cast<Text &>(pimpl_->inset.text_);
304 Inset & Buffer::inset() const
306 return const_cast<InsetText &>(pimpl_->inset);
310 BufferParams & Buffer::params()
312 return pimpl_->params;
316 BufferParams const & Buffer::params() const
318 return pimpl_->params;
322 ParagraphList & Buffer::paragraphs()
324 return text().paragraphs();
328 ParagraphList const & Buffer::paragraphs() const
330 return text().paragraphs();
334 LyXVC & Buffer::lyxvc()
336 return pimpl_->lyxvc;
340 LyXVC const & Buffer::lyxvc() const
342 return pimpl_->lyxvc;
346 string const & Buffer::temppath() const
348 return pimpl_->temppath;
352 TexRow & Buffer::texrow()
354 return pimpl_->texrow;
358 TexRow const & Buffer::texrow() const
360 return pimpl_->texrow;
364 TocBackend & Buffer::tocBackend()
366 return pimpl_->toc_backend;
370 TocBackend const & Buffer::tocBackend() const
372 return pimpl_->toc_backend;
376 EmbeddedFiles & Buffer::embeddedFiles()
378 return pimpl_->embedded_files;
382 EmbeddedFiles const & Buffer::embeddedFiles() const
384 return pimpl_->embedded_files;
388 Undo & Buffer::undo()
390 return pimpl_->undo_;
394 string Buffer::latexName(bool const no_path) const
396 string const name = changeExtension(makeLatexName(absFileName()), ".tex");
397 return no_path ? onlyFilename(name) : name;
401 pair<Buffer::LogType, string> Buffer::logName() const
403 string const filename = latexName(false);
405 if (filename.empty())
406 return make_pair(Buffer::latexlog, string());
408 string const path = temppath();
410 FileName const fname(addName(temppath(),
411 onlyFilename(changeExtension(filename,
413 FileName const bname(
414 addName(path, onlyFilename(
415 changeExtension(filename,
416 formats.extension("literate") + ".out"))));
418 // If no Latex log or Build log is newer, show Build log
420 if (bname.exists() &&
421 (!fname.exists() || fname.lastModified() < bname.lastModified())) {
422 LYXERR(Debug::FILES) << "Log name calculated as: " << bname << endl;
423 return make_pair(Buffer::buildlog, bname.absFilename());
425 LYXERR(Debug::FILES) << "Log name calculated as: " << fname << endl;
426 return make_pair(Buffer::latexlog, fname.absFilename());
430 void Buffer::setReadonly(bool const flag)
432 if (pimpl_->read_only != flag) {
433 pimpl_->read_only = flag;
439 void Buffer::setFileName(string const & newfile)
441 pimpl_->filename = makeAbsPath(newfile);
442 params().filepath = onlyPath(pimpl_->filename.absFilename());
443 setReadonly(pimpl_->filename.isReadOnly());
448 int Buffer::readHeader(Lexer & lex)
450 int unknown_tokens = 0;
452 int begin_header_line = -1;
454 // Initialize parameters that may be/go lacking in header:
455 params().branchlist().clear();
456 params().preamble.erase();
457 params().options.erase();
458 params().float_placement.erase();
459 params().paperwidth.erase();
460 params().paperheight.erase();
461 params().leftmargin.erase();
462 params().rightmargin.erase();
463 params().topmargin.erase();
464 params().bottommargin.erase();
465 params().headheight.erase();
466 params().headsep.erase();
467 params().footskip.erase();
468 params().listings_params.clear();
469 params().clearLayoutModules();
470 params().pdfoptions().clear();
472 for (int i = 0; i < 4; ++i) {
473 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
474 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
477 ErrorList & errorList = pimpl_->errorLists["Parse"];
481 string const token = lex.getString();
486 if (token == "\\end_header")
490 if (token == "\\begin_header") {
491 begin_header_line = line;
495 LYXERR(Debug::PARSER) << "Handling document header token: `"
496 << token << '\'' << endl;
498 string unknown = params().readToken(lex, token);
499 if (!unknown.empty()) {
500 if (unknown[0] != '\\' && token == "\\textclass") {
501 Alert::warning(_("Unknown document class"),
502 bformat(_("Using the default document class, because the "
503 "class %1$s is unknown."), from_utf8(unknown)));
506 docstring const s = bformat(_("Unknown token: "
510 errorList.push_back(ErrorItem(_("Document header error"),
515 if (begin_header_line) {
516 docstring const s = _("\\begin_header is missing");
517 errorList.push_back(ErrorItem(_("Document header error"),
521 return unknown_tokens;
526 // changed to be public and have one parameter
527 // Returns false if "\end_document" is not read (Asger)
528 bool Buffer::readDocument(Lexer & lex)
530 ErrorList & errorList = pimpl_->errorLists["Parse"];
534 string const token = lex.getString();
535 if (token != "\\begin_document") {
536 docstring const s = _("\\begin_document is missing");
537 errorList.push_back(ErrorItem(_("Document header error"),
541 // we are reading in a brand new document
542 BOOST_ASSERT(paragraphs().empty());
545 TextClass const & baseClass = textclasslist[params().getBaseClass()];
546 if (!baseClass.load(filePath())) {
547 string theclass = baseClass.name();
548 Alert::error(_("Can't load document class"), bformat(
549 _("Using the default document class, because the "
550 "class %1$s could not be loaded."), from_utf8(theclass)));
551 params().setBaseClass(defaultTextclass());
554 if (params().outputChanges) {
555 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
556 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
557 LaTeXFeatures::isAvailable("xcolor");
559 if (!dvipost && !xcolorsoul) {
560 Alert::warning(_("Changes not shown in LaTeX output"),
561 _("Changes will not be highlighted in LaTeX output, "
562 "because neither dvipost nor xcolor/soul are installed.\n"
563 "Please install these packages or redefine "
564 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
565 } else if (!xcolorsoul) {
566 Alert::warning(_("Changes not shown in LaTeX output"),
567 _("Changes will not be highlighted in LaTeX output "
568 "when using pdflatex, because xcolor and soul are not installed.\n"
569 "Please install both packages or redefine "
570 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
575 bool const res = text().read(*this, lex, errorList);
576 for_each(text().paragraphs().begin(),
577 text().paragraphs().end(),
578 bind(&Paragraph::setInsetOwner, _1, &inset()));
584 // needed to insert the selection
585 void Buffer::insertStringAsLines(ParagraphList & pars,
586 pit_type & pit, pos_type & pos,
587 Font const & fn, docstring const & str, bool autobreakrows)
591 // insert the string, don't insert doublespace
592 bool space_inserted = true;
593 for (docstring::const_iterator cit = str.begin();
594 cit != str.end(); ++cit) {
595 Paragraph & par = pars[pit];
597 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
598 breakParagraph(params(), pars, pit, pos,
599 par.layout()->isEnvironment());
602 space_inserted = true;
606 // do not insert consecutive spaces if !free_spacing
607 } else if ((*cit == ' ' || *cit == '\t') &&
608 space_inserted && !par.isFreeSpacing()) {
610 } else if (*cit == '\t') {
611 if (!par.isFreeSpacing()) {
612 // tabs are like spaces here
613 par.insertChar(pos, ' ', font, params().trackChanges);
615 space_inserted = true;
617 const pos_type n = 8 - pos % 8;
618 for (pos_type i = 0; i < n; ++i) {
619 par.insertChar(pos, ' ', font, params().trackChanges);
622 space_inserted = true;
624 } else if (!isPrintable(*cit)) {
625 // Ignore unprintables
628 // just insert the character
629 par.insertChar(pos, *cit, font, params().trackChanges);
631 space_inserted = (*cit == ' ');
638 bool Buffer::readString(std::string const & s)
640 params().compressed = false;
642 // remove dummy empty par
643 paragraphs().clear();
645 std::istringstream is(s);
647 FileName const name(tempName());
648 switch (readFile(lex, name, true)) {
652 // We need to call lyx2lyx, so write the input to a file
653 std::ofstream os(name.toFilesystemEncoding().c_str());
656 return readFile(name);
666 bool Buffer::readFile(FileName const & filename)
668 FileName fname(filename);
669 // Check if the file is compressed.
670 string format = filename.guessFormatFromContents();
671 if (format == "zip") {
672 // decompress to a temp directory
673 LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl;
674 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
676 FileName lyxfile(addName(temppath(), "content.lyx"));
677 // if both manifest.txt and file.lyx exist, this is am embedded file
678 if (lyxfile.exists()) {
679 params().embedded = true;
683 // The embedded lyx file can also be compressed, for backward compatibility
684 format = fname.guessFormatFromContents();
685 if (format == "gzip" || format == "zip" || format == "compress")
686 params().compressed = true;
688 // remove dummy empty par
689 paragraphs().clear();
692 if (readFile(lex, fname) != success)
699 bool Buffer::isFullyLoaded() const
701 return pimpl_->file_fully_loaded;
705 void Buffer::setFullyLoaded(bool value)
707 pimpl_->file_fully_loaded = value;
711 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
714 BOOST_ASSERT(!filename.empty());
717 Alert::error(_("Document could not be read"),
718 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
723 string const token = lex.getString();
726 Alert::error(_("Document could not be read"),
727 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
731 // the first token _must_ be...
732 if (token != "\\lyxformat") {
733 lyxerr << "Token: " << token << endl;
735 Alert::error(_("Document format failure"),
736 bformat(_("%1$s is not a LyX document."),
737 from_utf8(filename.absFilename())));
742 string tmp_format = lex.getString();
743 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
744 // if present remove ".," from string.
745 string::size_type dot = tmp_format.find_first_of(".,");
746 //lyxerr << " dot found at " << dot << endl;
747 if (dot != string::npos)
748 tmp_format.erase(dot, 1);
749 int const file_format = convert<int>(tmp_format);
750 //lyxerr << "format: " << file_format << endl;
752 // save timestamp and checksum of the original disk file, making sure
753 // to not overwrite them with those of the file created in the tempdir
754 // when it has to be converted to the current format.
755 if (!pimpl_->checksum_) {
756 // Save the timestamp and checksum of disk file. If filename is an
757 // emergency file, save the timestamp and sum of the original lyx file
758 // because isExternallyModified will check for this file. (BUG4193)
759 string diskfile = filename.toFilesystemEncoding();
760 if (suffixIs(diskfile, ".emergency"))
761 diskfile = diskfile.substr(0, diskfile.size() - 10);
762 saveCheckSum(FileName(diskfile));
765 if (file_format != LYX_FORMAT) {
768 // lyx2lyx would fail
771 FileName const tmpfile(tempName());
772 if (tmpfile.empty()) {
773 Alert::error(_("Conversion failed"),
774 bformat(_("%1$s is from a different"
775 " version of LyX, but a temporary"
776 " file for converting it could"
778 from_utf8(filename.absFilename())));
781 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
782 if (lyx2lyx.empty()) {
783 Alert::error(_("Conversion script not found"),
784 bformat(_("%1$s is from a different"
785 " version of LyX, but the"
786 " conversion script lyx2lyx"
787 " could not be found."),
788 from_utf8(filename.absFilename())));
791 ostringstream command;
792 command << os::python()
793 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
794 << " -t " << convert<string>(LYX_FORMAT)
795 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
796 << ' ' << quoteName(filename.toFilesystemEncoding());
797 string const command_str = command.str();
799 LYXERR(Debug::INFO) << "Running '"
800 << command_str << '\''
803 cmd_ret const ret = runCommand(command_str);
804 if (ret.first != 0) {
805 Alert::error(_("Conversion script failed"),
806 bformat(_("%1$s is from a different version"
807 " of LyX, but the lyx2lyx script"
808 " failed to convert it."),
809 from_utf8(filename.absFilename())));
812 bool const ret = readFile(tmpfile);
813 // Do stuff with tmpfile name and buffer name here.
814 return ret ? success : failure;
819 if (readDocument(lex)) {
820 Alert::error(_("Document format failure"),
821 bformat(_("%1$s ended unexpectedly, which means"
822 " that it is probably corrupted."),
823 from_utf8(filename.absFilename())));
826 //lyxerr << "removing " << MacroTable::localMacros().size()
827 // << " temporary macro entries" << endl;
828 //MacroTable::localMacros().clear();
830 pimpl_->file_fully_loaded = true;
835 // Should probably be moved to somewhere else: BufferView? LyXView?
836 bool Buffer::save() const
838 // We don't need autosaves in the immediate future. (Asger)
839 resetAutosaveTimers();
841 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
844 bool madeBackup = false;
846 // make a backup if the file already exists
847 if (lyxrc.make_backup && fs::exists(encodedFilename)) {
848 backupName = FileName(absFileName() + '~');
849 if (!lyxrc.backupdir_path.empty()) {
850 string const mangledName =
851 subst(subst(os::internal_path(
852 backupName.absFilename()), '/', '!'), ':', '!');
853 backupName = FileName(addName(lyxrc.backupdir_path,
857 fs::copy_file(encodedFilename, backupName.toFilesystemEncoding(), false);
859 } catch (fs::filesystem_error const & fe) {
860 Alert::error(_("Backup failure"),
861 bformat(_("Cannot create backup file %1$s.\n"
862 "Please check whether the directory exists and is writeable."),
863 from_utf8(backupName.absFilename())));
864 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
868 // ask if the disk file has been externally modified (use checksum method)
869 if (fs::exists(encodedFilename) && isExternallyModified(checksum_method)) {
870 docstring const file = makeDisplayPath(absFileName(), 20);
871 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
872 "you want to overwrite this file?"), file);
873 int const ret = Alert::prompt(_("Overwrite modified file?"),
874 text, 1, 1, _("&Overwrite"), _("&Cancel"));
879 if (writeFile(pimpl_->filename)) {
881 removeAutosaveFile(absFileName());
882 saveCheckSum(pimpl_->filename);
885 // Saving failed, so backup is not backup
887 rename(backupName, pimpl_->filename);
893 bool Buffer::writeFile(FileName const & fname) const
895 if (pimpl_->read_only && fname == pimpl_->filename)
901 if (params().embedded)
902 // first write the .lyx file to the temporary directory
903 content = FileName(addName(temppath(), "content.lyx"));
907 if (params().compressed) {
908 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
914 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
921 if (retval && params().embedded) {
922 // write file.lyx and all the embedded files to the zip file fname
923 // if embedding is enabled
924 return pimpl_->embedded_files.writeFile(fname);
930 bool Buffer::write(ostream & ofs) const
933 // Use the standard "C" locale for file output.
934 ofs.imbue(std::locale::classic());
937 // The top of the file should not be written by params().
939 // write out a comment in the top of the file
940 ofs << "#LyX " << lyx_version
941 << " created this file. For more info see http://www.lyx.org/\n"
942 << "\\lyxformat " << LYX_FORMAT << "\n"
943 << "\\begin_document\n";
946 /// For each author, set 'used' to true if there is a change
947 /// by this author in the document; otherwise set it to 'false'.
948 AuthorList::Authors::const_iterator a_it = params().authors().begin();
949 AuthorList::Authors::const_iterator a_end = params().authors().end();
950 for (; a_it != a_end; ++a_it)
951 a_it->second.used(false);
953 ParIterator const end = par_iterator_end();
954 ParIterator it = par_iterator_begin();
955 for ( ; it != end; ++it)
956 it->checkAuthors(params().authors());
958 // now write out the buffer parameters.
959 ofs << "\\begin_header\n";
960 params().writeFile(ofs);
961 ofs << "\\end_header\n";
964 ofs << "\n\\begin_body\n";
965 text().write(*this, ofs);
966 ofs << "\n\\end_body\n";
968 // Write marker that shows file is complete
969 ofs << "\\end_document" << endl;
971 // Shouldn't really be needed....
974 // how to check if close went ok?
975 // Following is an attempt... (BE 20001011)
977 // good() returns false if any error occured, including some
979 // bad() returns true if something bad happened in the buffer,
980 // which should include file system full errors.
985 lyxerr << "File was not closed properly." << endl;
992 bool Buffer::makeLaTeXFile(FileName const & fname,
993 string const & original_path,
994 OutputParams const & runparams,
995 bool output_preamble, bool output_body)
997 string const encoding = runparams.encoding->iconvName();
998 LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: "
999 << encoding << "..." << endl;
1001 odocfstream ofs(encoding);
1002 if (!openFileWrite(ofs, fname))
1005 //TexStream ts(ofs.rdbuf(), &texrow());
1007 bool failed_export = false;
1010 writeLaTeXSource(ofs, original_path,
1011 runparams, output_preamble, output_body);
1013 catch (iconv_codecvt_facet_exception & e) {
1014 lyxerr << "Caught iconv exception: " << e.what() << endl;
1015 failed_export = true;
1017 catch (std::exception const & e) {
1018 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1019 failed_export = true;
1022 lyxerr << "Caught some really weird exception..." << endl;
1023 LyX::cref().emergencyCleanup();
1029 failed_export = true;
1030 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1033 if (failed_export) {
1034 Alert::error(_("Encoding error"),
1035 _("Some characters of your document are probably not "
1036 "representable in the chosen encoding.\n"
1037 "Changing the document encoding to utf8 could help."));
1044 void Buffer::writeLaTeXSource(odocstream & os,
1045 string const & original_path,
1046 OutputParams const & runparams_in,
1047 bool const output_preamble, bool const output_body)
1049 OutputParams runparams = runparams_in;
1051 // validate the buffer.
1052 LYXERR(Debug::LATEX) << " Validating buffer..." << endl;
1053 LaTeXFeatures features(*this, params(), runparams);
1055 LYXERR(Debug::LATEX) << " Buffer validation done." << endl;
1057 // The starting paragraph of the coming rows is the
1058 // first paragraph of the document. (Asger)
1059 if (output_preamble && runparams.nice) {
1060 os << "%% LyX " << lyx_version << " created this file. "
1061 "For more info, see http://www.lyx.org/.\n"
1062 "%% Do not edit unless you really know what "
1067 LYXERR(Debug::INFO) << "lyx document header finished" << endl;
1068 // There are a few differences between nice LaTeX and usual files:
1069 // usual is \batchmode and has a
1070 // special input@path to allow the including of figures
1071 // with either \input or \includegraphics (what figinsets do).
1072 // input@path is set when the actual parameter
1073 // original_path is set. This is done for usual tex-file, but not
1074 // for nice-latex-file. (Matthias 250696)
1075 // Note that input@path is only needed for something the user does
1076 // in the preamble, included .tex files or ERT, files included by
1077 // LyX work without it.
1078 if (output_preamble) {
1079 if (!runparams.nice) {
1080 // code for usual, NOT nice-latex-file
1081 os << "\\batchmode\n"; // changed
1082 // from \nonstopmode
1085 if (!original_path.empty()) {
1087 // We don't know the encoding of inputpath
1088 docstring const inputpath = from_utf8(latex_path(original_path));
1089 os << "\\makeatletter\n"
1090 << "\\def\\input@path{{"
1091 << inputpath << "/}}\n"
1092 << "\\makeatother\n";
1098 // Write the preamble
1099 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1105 os << "\\begin{document}\n";
1107 } // output_preamble
1109 texrow().start(paragraphs().begin()->id(), 0);
1111 LYXERR(Debug::INFO) << "preamble finished, now the body." << endl;
1113 if (!lyxrc.language_auto_begin &&
1114 !params().language->babel().empty()) {
1116 os << from_utf8(subst(lyxrc.language_command_begin,
1118 params().language->babel()))
1123 Encoding const & encoding = params().encoding();
1124 if (encoding.package() == Encoding::CJK) {
1125 // Open a CJK environment, since in contrast to the encodings
1126 // handled by inputenc the document encoding is not set in
1127 // the preamble if it is handled by CJK.sty.
1128 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1133 // if we are doing a real file with body, even if this is the
1134 // child of some other buffer, let's cut the link here.
1135 // This happens for example if only a child document is printed.
1136 string save_parentname;
1137 if (output_preamble) {
1138 save_parentname = params().parentname;
1139 params().parentname.erase();
1142 loadChildDocuments();
1145 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1147 // Restore the parenthood if needed
1148 if (output_preamble)
1149 params().parentname = save_parentname;
1151 // add this just in case after all the paragraphs
1155 if (encoding.package() == Encoding::CJK) {
1156 // Close the open CJK environment.
1157 // latexParagraphs will have opened one even if the last text
1159 os << "\\end{CJK}\n";
1163 if (!lyxrc.language_auto_end &&
1164 !params().language->babel().empty()) {
1165 os << from_utf8(subst(lyxrc.language_command_end,
1167 params().language->babel()))
1172 if (output_preamble) {
1173 os << "\\end{document}\n";
1176 LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl;
1178 LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made."
1181 runparams_in.encoding = runparams.encoding;
1183 // Just to be sure. (Asger)
1186 LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl;
1187 LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1
1192 bool Buffer::isLatex() const
1194 return params().getTextClass().outputType() == LATEX;
1198 bool Buffer::isLiterate() const
1200 return params().getTextClass().outputType() == LITERATE;
1204 bool Buffer::isDocBook() const
1206 return params().getTextClass().outputType() == DOCBOOK;
1210 void Buffer::makeDocBookFile(FileName const & fname,
1211 OutputParams const & runparams,
1212 bool const body_only)
1214 LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl;
1218 if (!openFileWrite(ofs, fname))
1221 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1225 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1229 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1230 OutputParams const & runparams,
1231 bool const only_body)
1233 LaTeXFeatures features(*this, params(), runparams);
1238 TextClass const & tclass = params().getTextClass();
1239 string const top_element = tclass.latexname();
1242 if (runparams.flavor == OutputParams::XML)
1243 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1246 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1249 if (! tclass.class_header().empty())
1250 os << from_ascii(tclass.class_header());
1251 else if (runparams.flavor == OutputParams::XML)
1252 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1253 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1255 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1257 docstring preamble = from_utf8(params().preamble);
1258 if (runparams.flavor != OutputParams::XML ) {
1259 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1260 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1261 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1262 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1265 string const name = runparams.nice
1266 ? changeExtension(absFileName(), ".sgml") : fname;
1267 preamble += features.getIncludedFiles(name);
1268 preamble += features.getLyXSGMLEntities();
1270 if (!preamble.empty()) {
1271 os << "\n [ " << preamble << " ]";
1276 string top = top_element;
1278 if (runparams.flavor == OutputParams::XML)
1279 top += params().language->code();
1281 top += params().language->code().substr(0,2);
1284 if (!params().options.empty()) {
1286 top += params().options;
1289 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1290 << " file was created by LyX " << lyx_version
1291 << "\n See http://www.lyx.org/ for more information -->\n";
1293 params().getTextClass().counters().reset();
1295 loadChildDocuments();
1297 sgml::openTag(os, top);
1299 docbookParagraphs(paragraphs(), *this, os, runparams);
1300 sgml::closeTag(os, top_element);
1304 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1305 // Other flags: -wall -v0 -x
1306 int Buffer::runChktex()
1310 // get LaTeX-Filename
1311 FileName const path(temppath());
1312 string const name = addName(path.absFilename(), latexName());
1313 string const org_path = filePath();
1315 support::Path p(path); // path to LaTeX file
1316 message(_("Running chktex..."));
1318 // Generate the LaTeX file if neccessary
1319 OutputParams runparams(¶ms().encoding());
1320 runparams.flavor = OutputParams::LATEX;
1321 runparams.nice = false;
1322 makeLaTeXFile(FileName(name), org_path, runparams);
1325 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1326 int const res = chktex.run(terr); // run chktex
1329 Alert::error(_("chktex failure"),
1330 _("Could not run chktex successfully."));
1331 } else if (res > 0) {
1332 ErrorList & errlist = pimpl_->errorLists["ChkTeX"];
1334 bufferErrors(terr, errlist);
1345 void Buffer::validate(LaTeXFeatures & features) const
1347 TextClass const & tclass = params().getTextClass();
1349 if (params().outputChanges) {
1350 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1351 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1352 LaTeXFeatures::isAvailable("xcolor");
1354 if (features.runparams().flavor == OutputParams::LATEX) {
1356 features.require("ct-dvipost");
1357 features.require("dvipost");
1358 } else if (xcolorsoul) {
1359 features.require("ct-xcolor-soul");
1360 features.require("soul");
1361 features.require("xcolor");
1363 features.require("ct-none");
1365 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1367 features.require("ct-xcolor-soul");
1368 features.require("soul");
1369 features.require("xcolor");
1370 features.require("pdfcolmk"); // improves color handling in PDF output
1372 features.require("ct-none");
1377 // AMS Style is at document level
1378 if (params().use_amsmath == BufferParams::package_on
1379 || tclass.provides("amsmath"))
1380 features.require("amsmath");
1381 if (params().use_esint == BufferParams::package_on)
1382 features.require("esint");
1384 loadChildDocuments();
1386 for_each(paragraphs().begin(), paragraphs().end(),
1387 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1389 // the bullet shapes are buffer level not paragraph level
1390 // so they are tested here
1391 for (int i = 0; i < 4; ++i) {
1392 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1393 int const font = params().user_defined_bullet(i).getFont();
1395 int const c = params()
1396 .user_defined_bullet(i)
1403 features.require("latexsym");
1405 } else if (font == 1) {
1406 features.require("amssymb");
1407 } else if ((font >= 2 && font <= 5)) {
1408 features.require("pifont");
1413 if (lyxerr.debugging(Debug::LATEX)) {
1414 features.showStruct();
1419 void Buffer::getLabelList(vector<docstring> & list) const
1421 /// if this is a child document and the parent is already loaded
1422 /// Use the parent's list instead [ale990407]
1423 Buffer const * tmp = masterBuffer();
1425 lyxerr << "masterBuffer() failed!" << endl;
1429 tmp->getLabelList(list);
1433 loadChildDocuments();
1435 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1436 it.nextInset()->getLabelList(*this, list);
1440 void Buffer::updateBibfilesCache()
1442 // if this is a child document and the parent is already loaded
1443 // update the parent's cache instead
1444 Buffer * tmp = masterBuffer();
1447 tmp->updateBibfilesCache();
1451 bibfilesCache_.clear();
1452 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1453 if (it->lyxCode() == BIBTEX_CODE) {
1454 InsetBibtex const & inset =
1455 static_cast<InsetBibtex const &>(*it);
1456 vector<FileName> const bibfiles = inset.getFiles(*this);
1457 bibfilesCache_.insert(bibfilesCache_.end(),
1460 } else if (it->lyxCode() == INCLUDE_CODE) {
1461 InsetInclude & inset =
1462 static_cast<InsetInclude &>(*it);
1463 inset.updateBibfilesCache(*this);
1464 vector<FileName> const & bibfiles =
1465 inset.getBibfilesCache(*this);
1466 bibfilesCache_.insert(bibfilesCache_.end(),
1474 vector<FileName> const & Buffer::getBibfilesCache() const
1476 // if this is a child document and the parent is already loaded
1477 // use the parent's cache instead
1478 Buffer const * tmp = masterBuffer();
1481 return tmp->getBibfilesCache();
1483 // We update the cache when first used instead of at loading time.
1484 if (bibfilesCache_.empty())
1485 const_cast<Buffer *>(this)->updateBibfilesCache();
1487 return bibfilesCache_;
1491 bool Buffer::isDepClean(string const & name) const
1493 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1494 if (it == pimpl_->dep_clean.end())
1500 void Buffer::markDepClean(string const & name)
1502 pimpl_->dep_clean[name] = true;
1506 bool Buffer::dispatch(string const & command, bool * result)
1508 return dispatch(lyxaction.lookupFunc(command), result);
1512 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1514 bool dispatched = true;
1516 switch (func.action) {
1517 case LFUN_BUFFER_EXPORT: {
1518 bool const tmp = doExport(to_utf8(func.argument()), false);
1531 void Buffer::changeLanguage(Language const * from, Language const * to)
1536 for_each(par_iterator_begin(),
1538 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1542 bool Buffer::isMultiLingual() const
1544 ParConstIterator end = par_iterator_end();
1545 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1546 if (it->isMultiLingual(params()))
1553 ParIterator Buffer::getParFromID(int const id) const
1555 ParConstIterator it = par_iterator_begin();
1556 ParConstIterator const end = par_iterator_end();
1559 // John says this is called with id == -1 from undo
1560 lyxerr << "getParFromID(), id: " << id << endl;
1564 for (; it != end; ++it)
1572 bool Buffer::hasParWithID(int const id) const
1574 ParConstIterator const it = getParFromID(id);
1575 return it != par_iterator_end();
1579 ParIterator Buffer::par_iterator_begin()
1581 return lyx::par_iterator_begin(inset());
1585 ParIterator Buffer::par_iterator_end()
1587 return lyx::par_iterator_end(inset());
1591 ParConstIterator Buffer::par_iterator_begin() const
1593 return lyx::par_const_iterator_begin(inset());
1597 ParConstIterator Buffer::par_iterator_end() const
1599 return lyx::par_const_iterator_end(inset());
1603 Language const * Buffer::language() const
1605 return params().language;
1609 docstring const Buffer::B_(string const & l10n) const
1611 return params().B_(l10n);
1615 bool Buffer::isClean() const
1617 return pimpl_->lyx_clean;
1621 bool Buffer::isBakClean() const
1623 return pimpl_->bak_clean;
1627 bool Buffer::isExternallyModified(CheckMethod method) const
1629 BOOST_ASSERT(pimpl_->filename.exists());
1630 // if method == timestamp, check timestamp before checksum
1631 return (method == checksum_method
1632 || pimpl_->timestamp_ != pimpl_->filename.lastModified())
1633 && pimpl_->checksum_ != sum(pimpl_->filename);
1637 void Buffer::saveCheckSum(FileName const & file) const
1639 if (file.exists()) {
1640 pimpl_->timestamp_ = file.lastModified();
1641 pimpl_->checksum_ = sum(file);
1643 // in the case of save to a new file.
1644 pimpl_->timestamp_ = 0;
1645 pimpl_->checksum_ = 0;
1650 void Buffer::markClean() const
1652 if (!pimpl_->lyx_clean) {
1653 pimpl_->lyx_clean = true;
1656 // if the .lyx file has been saved, we don't need an
1658 pimpl_->bak_clean = true;
1662 void Buffer::markBakClean() const
1664 pimpl_->bak_clean = true;
1668 void Buffer::setUnnamed(bool flag)
1670 pimpl_->unnamed = flag;
1674 bool Buffer::isUnnamed() const
1676 return pimpl_->unnamed;
1680 // FIXME: this function should be moved to buffer_pimpl.C
1681 void Buffer::markDirty()
1683 if (pimpl_->lyx_clean) {
1684 pimpl_->lyx_clean = false;
1687 pimpl_->bak_clean = false;
1689 DepClean::iterator it = pimpl_->dep_clean.begin();
1690 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1692 for (; it != end; ++it)
1697 string Buffer::absFileName() const
1699 return pimpl_->filename.absFilename();
1703 string const & Buffer::filePath() const
1705 return params().filepath;
1709 bool Buffer::isReadonly() const
1711 return pimpl_->read_only;
1715 void Buffer::setParentName(string const & name)
1717 if (name == pimpl_->filename.absFilename())
1718 // Avoids recursive include.
1719 params().parentname.clear();
1721 params().parentname = name;
1725 Buffer const * Buffer::masterBuffer() const
1727 if (!params().parentname.empty()
1728 && theBufferList().exists(params().parentname)) {
1729 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1730 //We need to check if the parent is us...
1731 //FIXME RECURSIVE INCLUDE
1732 //This is not sufficient, since recursive includes could be downstream.
1733 if (buf && buf != this)
1734 return buf->masterBuffer();
1741 Buffer * Buffer::masterBuffer()
1743 if (!params().parentname.empty()
1744 && theBufferList().exists(params().parentname)) {
1745 Buffer * buf = theBufferList().getBuffer(params().parentname);
1746 //We need to check if the parent is us...
1747 //FIXME RECURSIVE INCLUDE
1748 //This is not sufficient, since recursive includes could be downstream.
1749 if (buf && buf != this)
1750 return buf->masterBuffer();
1757 MacroData const & Buffer::getMacro(docstring const & name) const
1759 return pimpl_->macros.get(name);
1763 bool Buffer::hasMacro(docstring const & name) const
1765 return pimpl_->macros.has(name);
1769 void Buffer::insertMacro(docstring const & name, MacroData const & data)
1771 MacroTable::globalMacros().insert(name, data);
1772 pimpl_->macros.insert(name, data);
1776 void Buffer::buildMacros()
1778 // Start with global table.
1779 pimpl_->macros = MacroTable::globalMacros();
1782 ParagraphList const & pars = text().paragraphs();
1783 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1784 //lyxerr << "searching main par " << i
1785 // << " for macro definitions" << std::endl;
1786 InsetList const & insets = pars[i].insetList();
1787 InsetList::const_iterator it = insets.begin();
1788 InsetList::const_iterator end = insets.end();
1789 for ( ; it != end; ++it) {
1790 //lyxerr << "found inset code " << it->inset->lyxCode() << std::endl;
1791 if (it->inset->lyxCode() == MATHMACRO_CODE) {
1792 MathMacroTemplate const & mac
1793 = static_cast<MathMacroTemplate const &>(*it->inset);
1794 insertMacro(mac.name(), mac.asMacroData());
1801 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1804 //FIXME: This does not work for child documents yet.
1805 BOOST_ASSERT(code == CITE_CODE || code == REF_CODE);
1806 // Check if the label 'from' appears more than once
1807 vector<docstring> labels;
1810 if (code == CITE_CODE) {
1812 keys.fillWithBibKeys(this);
1813 BiblioInfo::const_iterator bit = keys.begin();
1814 BiblioInfo::const_iterator bend = keys.end();
1816 for (; bit != bend; ++bit)
1818 labels.push_back(bit->first);
1821 getLabelList(labels);
1822 paramName = "reference";
1825 if (std::count(labels.begin(), labels.end(), from) > 1)
1828 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1829 if (it->lyxCode() == code) {
1830 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1831 docstring const oldValue = inset.getParam(paramName);
1832 if (oldValue == from)
1833 inset.setParam(paramName, to);
1839 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1840 pit_type par_end, bool full_source)
1842 OutputParams runparams(¶ms().encoding());
1843 runparams.nice = true;
1844 runparams.flavor = OutputParams::LATEX;
1845 runparams.linelen = lyxrc.plaintext_linelen;
1846 // No side effect of file copying and image conversion
1847 runparams.dryrun = true;
1851 os << "% " << _("Preview source code") << "\n\n";
1855 writeLaTeXSource(os, filePath(), runparams, true, true);
1857 writeDocBookSource(os, absFileName(), runparams, false);
1860 runparams.par_begin = par_begin;
1861 runparams.par_end = par_end;
1862 if (par_begin + 1 == par_end)
1864 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1868 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1869 convert<docstring>(par_begin),
1870 convert<docstring>(par_end - 1))
1874 // output paragraphs
1876 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1879 docbookParagraphs(paragraphs(), *this, os, runparams);
1885 ErrorList const & Buffer::errorList(string const & type) const
1887 static ErrorList const emptyErrorList;
1888 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1889 if (I == pimpl_->errorLists.end())
1890 return emptyErrorList;
1896 ErrorList & Buffer::errorList(string const & type)
1898 return pimpl_->errorLists[type];
1902 void Buffer::structureChanged() const
1905 gui_->structureChanged();
1909 void Buffer::errors(std::string const & err) const
1916 void Buffer::message(docstring const & msg) const
1923 void Buffer::setBusy(bool on) const
1930 void Buffer::setReadOnly(bool on) const
1933 gui_->setReadOnly(on);
1937 void Buffer::updateTitles() const
1940 gui_->updateTitles();
1944 void Buffer::resetAutosaveTimers() const
1947 gui_->resetAutosaveTimers();
1951 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
1960 class AutoSaveBuffer : public support::ForkedProcess {
1963 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
1964 : buffer_(buffer), fname_(fname) {}
1966 virtual boost::shared_ptr<ForkedProcess> clone() const
1968 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
1973 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
1974 from_utf8(fname_.absFilename())));
1975 return run(DontWait);
1979 virtual int generateChild();
1981 Buffer const & buffer_;
1986 #if !defined (HAVE_FORK)
1990 int AutoSaveBuffer::generateChild()
1992 // tmp_ret will be located (usually) in /tmp
1993 // will that be a problem?
1994 pid_t const pid = fork();
1995 // If you want to debug the autosave
1996 // you should set pid to -1, and comment out the fork.
1997 if (pid == 0 || pid == -1) {
1998 // pid = -1 signifies that lyx was unable
1999 // to fork. But we will do the save
2001 bool failed = false;
2003 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2004 if (!tmp_ret.empty()) {
2005 buffer_.writeFile(tmp_ret);
2006 // assume successful write of tmp_ret
2007 if (!rename(tmp_ret, fname_)) {
2009 // most likely couldn't move between
2010 // filesystems unless write of tmp_ret
2011 // failed so remove tmp file (if it
2020 // failed to write/rename tmp_ret so try writing direct
2021 if (!buffer_.writeFile(fname_)) {
2022 // It is dangerous to do this in the child,
2023 // but safe in the parent, so...
2024 if (pid == -1) // emit message signal.
2025 buffer_.message(_("Autosave failed!"));
2028 if (pid == 0) { // we are the child so...
2038 // Perfect target for a thread...
2039 void Buffer::autoSave() const
2041 if (isBakClean() || isReadonly()) {
2042 // We don't save now, but we'll try again later
2043 resetAutosaveTimers();
2047 // emit message signal.
2048 message(_("Autosaving current document..."));
2050 // create autosave filename
2051 string fname = filePath();
2053 fname += onlyFilename(absFileName());
2056 AutoSaveBuffer autosave(*this, FileName(fname));
2060 resetAutosaveTimers();
2064 /** Write a buffer to a new file name and rename the buffer
2065 according to the new file name.
2067 This function is e.g. used by menu callbacks and
2068 LFUN_BUFFER_WRITE_AS.
2070 If 'newname' is empty (the default), the user is asked via a
2071 dialog for the buffer's new name and location.
2073 If 'newname' is non-empty and has an absolute path, that is used.
2074 Otherwise the base directory of the buffer is used as the base
2075 for any relative path in 'newname'.
2078 bool Buffer::writeAs(string const & newname)
2080 string fname = absFileName();
2081 string const oldname = fname;
2083 if (newname.empty()) { /// No argument? Ask user through dialog
2086 FileDialog dlg(_("Choose a filename to save document as"),
2087 LFUN_BUFFER_WRITE_AS);
2088 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
2089 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
2091 if (!support::isLyXFilename(fname))
2094 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2096 FileDialog::Result result =
2097 dlg.save(from_utf8(onlyPath(fname)),
2099 from_utf8(onlyFilename(fname)));
2101 if (result.first == FileDialog::Later)
2104 fname = to_utf8(result.second);
2109 // Make sure the absolute filename ends with appropriate suffix
2110 fname = makeAbsPath(fname).absFilename();
2111 if (!support::isLyXFilename(fname))
2115 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2117 if (FileName(fname).exists()) {
2118 docstring const file = makeDisplayPath(fname, 30);
2119 docstring text = bformat(_("The document %1$s already "
2120 "exists.\n\nDo you want to "
2121 "overwrite that document?"),
2123 int const ret = Alert::prompt(_("Overwrite document?"),
2124 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2130 // Ok, change the name of the buffer
2133 bool unnamed = isUnnamed();
2135 saveCheckSum(FileName(fname));
2138 setFileName(oldname);
2139 setUnnamed(unnamed);
2140 saveCheckSum(FileName(oldname));
2144 removeAutosaveFile(oldname);
2149 bool Buffer::menuWrite()
2152 LyX::ref().session().lastFiles().add(FileName(absFileName()));
2156 // FIXME: we don't tell the user *WHY* the save failed !!
2158 docstring const file = makeDisplayPath(absFileName(), 30);
2160 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2161 "Do you want to rename the document and "
2162 "try again?"), file);
2163 int const ret = Alert::prompt(_("Rename and save?"),
2164 text, 0, 1, _("&Rename"), _("&Cancel"));
2173 void Buffer::loadChildDocuments() const
2175 bool parse_error = false;
2177 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2178 if (it->lyxCode() != INCLUDE_CODE)
2180 InsetCommand const & inset = static_cast<InsetCommand const &>(*it);
2181 InsetCommandParams const & ip = inset.params();
2182 Buffer * child = loadIfNeeded(*this, ip);
2185 parse_error |= !child->errorList("Parse").empty();
2186 child->loadChildDocuments();
2189 if (use_gui && masterBuffer() == this)
2190 updateLabels(*this);
2194 string Buffer::bufferFormat() const
2204 bool Buffer::doExport(string const & format, bool put_in_tempdir,
2205 string & result_file)
2207 string backend_format;
2208 OutputParams runparams(¶ms().encoding());
2209 runparams.flavor = OutputParams::LATEX;
2210 runparams.linelen = lyxrc.plaintext_linelen;
2211 vector<string> backs = backends();
2212 if (find(backs.begin(), backs.end(), format) == backs.end()) {
2213 // Get shortest path to format
2214 Graph::EdgePath path;
2215 for (vector<string>::const_iterator it = backs.begin();
2216 it != backs.end(); ++it) {
2217 Graph::EdgePath p = theConverters().getPath(*it, format);
2218 if (!p.empty() && (path.empty() || p.size() < path.size())) {
2219 backend_format = *it;
2224 runparams.flavor = theConverters().getFlavor(path);
2226 Alert::error(_("Couldn't export file"),
2227 bformat(_("No information for exporting the format %1$s."),
2228 formats.prettyName(format)));
2232 backend_format = format;
2233 // FIXME: Don't hardcode format names here, but use a flag
2234 if (backend_format == "pdflatex")
2235 runparams.flavor = OutputParams::PDFLATEX;
2238 string filename = latexName(false);
2239 filename = addName(temppath(), filename);
2240 filename = changeExtension(filename,
2241 formats.extension(backend_format));
2243 // Plain text backend
2244 if (backend_format == "text")
2245 writePlaintextFile(*this, FileName(filename), runparams);
2247 else if (backend_format == "lyx")
2248 writeFile(FileName(filename));
2250 else if (isDocBook()) {
2251 runparams.nice = !put_in_tempdir;
2252 makeDocBookFile(FileName(filename), runparams);
2255 else if (backend_format == format) {
2256 runparams.nice = true;
2257 if (!makeLaTeXFile(FileName(filename), string(), runparams))
2259 } else if (!lyxrc.tex_allows_spaces
2260 && support::contains(filePath(), ' ')) {
2261 Alert::error(_("File name error"),
2262 _("The directory path to the document cannot contain spaces."));
2265 runparams.nice = false;
2266 if (!makeLaTeXFile(FileName(filename), filePath(), runparams))
2270 string const error_type = (format == "program")
2271 ? "Build" : bufferFormat();
2272 string const ext = formats.extension(format);
2273 FileName const tmp_result_file(changeExtension(filename, ext));
2274 bool const success = theConverters().convert(this, FileName(filename),
2275 tmp_result_file, FileName(absFileName()), backend_format, format,
2276 errorList(error_type));
2277 // Emit the signal to show the error list.
2278 if (format != backend_format)
2284 result_file = tmp_result_file.absFilename();
2286 result_file = changeExtension(absFileName(), ext);
2287 // We need to copy referenced files (e. g. included graphics
2288 // if format == "dvi") to the result dir.
2289 vector<ExportedFile> const files =
2290 runparams.exportdata->externalFiles(format);
2291 string const dest = onlyPath(result_file);
2292 CopyStatus status = SUCCESS;
2293 for (vector<ExportedFile>::const_iterator it = files.begin();
2294 it != files.end() && status != CANCEL; ++it) {
2296 formats.getFormatFromFile(it->sourceName);
2297 status = copyFile(fmt, it->sourceName,
2298 makeAbsPath(it->exportName, dest),
2299 it->exportName, status == FORCE);
2301 if (status == CANCEL) {
2302 message(_("Document export cancelled."));
2303 } else if (tmp_result_file.exists()) {
2304 // Finally copy the main file
2305 status = copyFile(format, tmp_result_file,
2306 FileName(result_file), result_file,
2308 message(bformat(_("Document exported as %1$s "
2310 formats.prettyName(format),
2311 makeDisplayPath(result_file)));
2313 // This must be a dummy converter like fax (bug 1888)
2314 message(bformat(_("Document exported as %1$s"),
2315 formats.prettyName(format)));
2323 bool Buffer::doExport(string const & format, bool put_in_tempdir)
2326 return doExport(format, put_in_tempdir, result_file);
2330 bool Buffer::preview(string const & format)
2333 if (!doExport(format, true, result_file))
2335 return formats.view(*this, FileName(result_file), format);
2339 bool Buffer::isExportable(string const & format) const
2341 vector<string> backs = backends();
2342 for (vector<string>::const_iterator it = backs.begin();
2343 it != backs.end(); ++it)
2344 if (theConverters().isReachable(*it, format))
2350 vector<Format const *> Buffer::exportableFormats(bool only_viewable) const
2352 vector<string> backs = backends();
2353 vector<Format const *> result =
2354 theConverters().getReachable(backs[0], only_viewable, true);
2355 for (vector<string>::const_iterator it = backs.begin() + 1;
2356 it != backs.end(); ++it) {
2357 vector<Format const *> r =
2358 theConverters().getReachable(*it, only_viewable, false);
2359 result.insert(result.end(), r.begin(), r.end());
2365 vector<string> Buffer::backends() const
2368 if (params().getTextClass().isTeXClassAvailable()) {
2369 v.push_back(bufferFormat());
2370 // FIXME: Don't hardcode format names here, but use a flag
2371 if (v.back() == "latex")
2372 v.push_back("pdflatex");
2374 v.push_back("text");
2380 bool Buffer::readFileHelper(FileName const & s)
2382 // File information about normal file
2384 docstring const file = makeDisplayPath(s.absFilename(), 50);
2385 docstring text = bformat(_("The specified document\n%1$s"
2386 "\ncould not be read."), file);
2387 Alert::error(_("Could not read document"), text);
2391 // Check if emergency save file exists and is newer.
2392 FileName const e(s.absFilename() + ".emergency");
2394 if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) {
2395 docstring const file = makeDisplayPath(s.absFilename(), 20);
2396 docstring const text =
2397 bformat(_("An emergency save of the document "
2399 "Recover emergency save?"), file);
2400 switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
2401 _("&Recover"), _("&Load Original"),
2405 // the file is not saved if we load the emergency file.
2415 // Now check if autosave file is newer.
2416 FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#');
2418 if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) {
2419 docstring const file = makeDisplayPath(s.absFilename(), 20);
2420 docstring const text =
2421 bformat(_("The backup of the document "
2422 "%1$s is newer.\n\nLoad the "
2423 "backup instead?"), file);
2424 switch (Alert::prompt(_("Load backup?"), text, 0, 2,
2425 _("&Load backup"), _("Load &original"),
2429 // the file is not saved if we load the autosave file.
2433 // Here we delete the autosave
2444 bool Buffer::loadLyXFile(FileName const & s)
2446 if (s.isReadable()) {
2447 if (readFileHelper(s)) {
2448 lyxvc().file_found_hook(s);
2449 if (!s.isWritable())
2454 docstring const file = makeDisplayPath(s.absFilename(), 20);
2455 // Here we probably should run
2456 if (LyXVC::file_not_found_hook(s)) {
2457 docstring const text =
2458 bformat(_("Do you want to retrieve the document"
2459 " %1$s from version control?"), file);
2460 int const ret = Alert::prompt(_("Retrieve from version control?"),
2461 text, 0, 1, _("&Retrieve"), _("&Cancel"));
2464 // How can we know _how_ to do the checkout?
2465 // With the current VC support it has to be,
2466 // a RCS file since CVS do not have special ,v files.
2468 return loadLyXFile(s);
2476 void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
2478 TeXErrors::Errors::const_iterator cit = terr.begin();
2479 TeXErrors::Errors::const_iterator end = terr.end();
2481 for (; cit != end; ++cit) {
2484 int errorRow = cit->error_in_line;
2485 bool found = texrow().getIdFromRow(errorRow, id_start,
2491 found = texrow().getIdFromRow(errorRow, id_end, pos_end);
2492 } while (found && id_start == id_end && pos_start == pos_end);
2494 errorList.push_back(ErrorItem(cit->error_desc,
2495 cit->error_text, id_start, pos_start, pos_end));