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 = 295; //Uwe: htmlurl, href
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."));
573 // read manifest after header
574 embeddedFiles().readManifest(lex, errorList);
577 bool const res = text().read(*this, lex, errorList);
578 for_each(text().paragraphs().begin(),
579 text().paragraphs().end(),
580 bind(&Paragraph::setInsetOwner, _1, &inset()));
586 // needed to insert the selection
587 void Buffer::insertStringAsLines(ParagraphList & pars,
588 pit_type & pit, pos_type & pos,
589 Font const & fn, docstring const & str, bool autobreakrows)
593 // insert the string, don't insert doublespace
594 bool space_inserted = true;
595 for (docstring::const_iterator cit = str.begin();
596 cit != str.end(); ++cit) {
597 Paragraph & par = pars[pit];
599 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
600 breakParagraph(params(), pars, pit, pos,
601 par.layout()->isEnvironment());
604 space_inserted = true;
608 // do not insert consecutive spaces if !free_spacing
609 } else if ((*cit == ' ' || *cit == '\t') &&
610 space_inserted && !par.isFreeSpacing()) {
612 } else if (*cit == '\t') {
613 if (!par.isFreeSpacing()) {
614 // tabs are like spaces here
615 par.insertChar(pos, ' ', font, params().trackChanges);
617 space_inserted = true;
619 const pos_type n = 8 - pos % 8;
620 for (pos_type i = 0; i < n; ++i) {
621 par.insertChar(pos, ' ', font, params().trackChanges);
624 space_inserted = true;
626 } else if (!isPrintable(*cit)) {
627 // Ignore unprintables
630 // just insert the character
631 par.insertChar(pos, *cit, font, params().trackChanges);
633 space_inserted = (*cit == ' ');
640 bool Buffer::readString(std::string const & s)
642 params().compressed = false;
644 // remove dummy empty par
645 paragraphs().clear();
647 std::istringstream is(s);
649 FileName const name(tempName());
650 switch (readFile(lex, name, true)) {
654 // We need to call lyx2lyx, so write the input to a file
655 std::ofstream os(name.toFilesystemEncoding().c_str());
658 return readFile(name);
668 bool Buffer::readFile(FileName const & filename)
670 FileName fname(filename);
671 // Check if the file is compressed.
672 string format = filename.guessFormatFromContents();
673 if (format == "zip") {
674 // decompress to a temp directory
675 LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl;
676 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
678 FileName lyxfile(addName(temppath(), "content.lyx"));
679 // if both manifest.txt and file.lyx exist, this is am embedded file
680 if (lyxfile.exists()) {
681 params().embedded = true;
685 // The embedded lyx file can also be compressed, for backward compatibility
686 format = fname.guessFormatFromContents();
687 if (format == "gzip" || format == "zip" || format == "compress")
688 params().compressed = true;
690 // remove dummy empty par
691 paragraphs().clear();
694 if (readFile(lex, fname) != success)
701 bool Buffer::isFullyLoaded() const
703 return pimpl_->file_fully_loaded;
707 void Buffer::setFullyLoaded(bool value)
709 pimpl_->file_fully_loaded = value;
713 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
716 BOOST_ASSERT(!filename.empty());
719 Alert::error(_("Document could not be read"),
720 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
725 string const token = lex.getString();
728 Alert::error(_("Document could not be read"),
729 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
733 // the first token _must_ be...
734 if (token != "\\lyxformat") {
735 lyxerr << "Token: " << token << endl;
737 Alert::error(_("Document format failure"),
738 bformat(_("%1$s is not a LyX document."),
739 from_utf8(filename.absFilename())));
744 string tmp_format = lex.getString();
745 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
746 // if present remove ".," from string.
747 string::size_type dot = tmp_format.find_first_of(".,");
748 //lyxerr << " dot found at " << dot << endl;
749 if (dot != string::npos)
750 tmp_format.erase(dot, 1);
751 int const file_format = convert<int>(tmp_format);
752 //lyxerr << "format: " << file_format << endl;
754 // save timestamp and checksum of the original disk file, making sure
755 // to not overwrite them with those of the file created in the tempdir
756 // when it has to be converted to the current format.
757 if (!pimpl_->checksum_) {
758 // Save the timestamp and checksum of disk file. If filename is an
759 // emergency file, save the timestamp and sum of the original lyx file
760 // because isExternallyModified will check for this file. (BUG4193)
761 string diskfile = filename.toFilesystemEncoding();
762 if (suffixIs(diskfile, ".emergency"))
763 diskfile = diskfile.substr(0, diskfile.size() - 10);
764 saveCheckSum(FileName(diskfile));
767 if (file_format != LYX_FORMAT) {
770 // lyx2lyx would fail
773 FileName const tmpfile(tempName());
774 if (tmpfile.empty()) {
775 Alert::error(_("Conversion failed"),
776 bformat(_("%1$s is from a different"
777 " version of LyX, but a temporary"
778 " file for converting it could"
780 from_utf8(filename.absFilename())));
783 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
784 if (lyx2lyx.empty()) {
785 Alert::error(_("Conversion script not found"),
786 bformat(_("%1$s is from a different"
787 " version of LyX, but the"
788 " conversion script lyx2lyx"
789 " could not be found."),
790 from_utf8(filename.absFilename())));
793 ostringstream command;
794 command << os::python()
795 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
796 << " -t " << convert<string>(LYX_FORMAT)
797 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
798 << ' ' << quoteName(filename.toFilesystemEncoding());
799 string const command_str = command.str();
801 LYXERR(Debug::INFO) << "Running '"
802 << command_str << '\''
805 cmd_ret const ret = runCommand(command_str);
806 if (ret.first != 0) {
807 Alert::error(_("Conversion script failed"),
808 bformat(_("%1$s is from a different version"
809 " of LyX, but the lyx2lyx script"
810 " failed to convert it."),
811 from_utf8(filename.absFilename())));
814 bool const ret = readFile(tmpfile);
815 // Do stuff with tmpfile name and buffer name here.
816 return ret ? success : failure;
821 if (readDocument(lex)) {
822 Alert::error(_("Document format failure"),
823 bformat(_("%1$s ended unexpectedly, which means"
824 " that it is probably corrupted."),
825 from_utf8(filename.absFilename())));
828 //lyxerr << "removing " << MacroTable::localMacros().size()
829 // << " temporary macro entries" << endl;
830 //MacroTable::localMacros().clear();
832 pimpl_->file_fully_loaded = true;
837 // Should probably be moved to somewhere else: BufferView? LyXView?
838 bool Buffer::save() const
840 // We don't need autosaves in the immediate future. (Asger)
841 resetAutosaveTimers();
843 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
846 bool madeBackup = false;
848 // make a backup if the file already exists
849 if (lyxrc.make_backup && fs::exists(encodedFilename)) {
850 backupName = FileName(absFileName() + '~');
851 if (!lyxrc.backupdir_path.empty())
852 backupName = FileName(addName(lyxrc.backupdir_path,
853 subst(os::internal_path(backupName.absFilename()), '/', '!')));
856 fs::copy_file(encodedFilename, backupName.toFilesystemEncoding(), false);
858 } catch (fs::filesystem_error const & fe) {
859 Alert::error(_("Backup failure"),
860 bformat(_("Cannot create backup file %1$s.\n"
861 "Please check whether the directory exists and is writeable."),
862 from_utf8(backupName.absFilename())));
863 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
867 // ask if the disk file has been externally modified (use checksum method)
868 if (fs::exists(encodedFilename) && isExternallyModified(checksum_method)) {
869 docstring const file = makeDisplayPath(absFileName(), 20);
870 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
871 "you want to overwrite this file?"), file);
872 int const ret = Alert::prompt(_("Overwrite modified file?"),
873 text, 1, 1, _("&Overwrite"), _("&Cancel"));
878 if (writeFile(pimpl_->filename)) {
880 removeAutosaveFile(absFileName());
881 saveCheckSum(pimpl_->filename);
884 // Saving failed, so backup is not backup
886 rename(backupName, pimpl_->filename);
892 bool Buffer::writeFile(FileName const & fname) const
894 if (pimpl_->read_only && fname == pimpl_->filename)
900 if (params().embedded)
901 // first write the .lyx file to the temporary directory
902 content = FileName(addName(temppath(), "content.lyx"));
906 if (params().compressed) {
907 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
913 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
920 if (retval && params().embedded) {
921 // write file.lyx and all the embedded files to the zip file fname
922 // if embedding is enabled
923 return pimpl_->embedded_files.writeFile(fname);
929 bool Buffer::write(ostream & ofs) const
932 // Use the standard "C" locale for file output.
933 ofs.imbue(std::locale::classic());
936 // The top of the file should not be written by params().
938 // write out a comment in the top of the file
939 ofs << "#LyX " << lyx_version
940 << " created this file. For more info see http://www.lyx.org/\n"
941 << "\\lyxformat " << LYX_FORMAT << "\n"
942 << "\\begin_document\n";
945 /// For each author, set 'used' to true if there is a change
946 /// by this author in the document; otherwise set it to 'false'.
947 AuthorList::Authors::const_iterator a_it = params().authors().begin();
948 AuthorList::Authors::const_iterator a_end = params().authors().end();
949 for (; a_it != a_end; ++a_it)
950 a_it->second.used(false);
952 ParIterator const end = par_iterator_end();
953 ParIterator it = par_iterator_begin();
954 for ( ; it != end; ++it)
955 it->checkAuthors(params().authors());
957 // now write out the buffer parameters.
958 ofs << "\\begin_header\n";
959 params().writeFile(ofs);
960 ofs << "\\end_header\n";
962 // write the manifest after header
963 ofs << "\n\\begin_manifest\n";
964 pimpl_->embedded_files.update();
965 embeddedFiles().writeManifest(ofs);
966 ofs << "\\end_manifest\n";
969 ofs << "\n\\begin_body\n";
970 text().write(*this, ofs);
971 ofs << "\n\\end_body\n";
973 // Write marker that shows file is complete
974 ofs << "\\end_document" << endl;
976 // Shouldn't really be needed....
979 // how to check if close went ok?
980 // Following is an attempt... (BE 20001011)
982 // good() returns false if any error occured, including some
984 // bad() returns true if something bad happened in the buffer,
985 // which should include file system full errors.
990 lyxerr << "File was not closed properly." << endl;
997 bool Buffer::makeLaTeXFile(FileName const & fname,
998 string const & original_path,
999 OutputParams const & runparams,
1000 bool output_preamble, bool output_body)
1002 string const encoding = runparams.encoding->iconvName();
1003 LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: "
1004 << encoding << "..." << endl;
1006 odocfstream ofs(encoding);
1007 if (!openFileWrite(ofs, fname))
1010 //TexStream ts(ofs.rdbuf(), &texrow());
1012 bool failed_export = false;
1015 writeLaTeXSource(ofs, original_path,
1016 runparams, output_preamble, output_body);
1018 catch (iconv_codecvt_facet_exception & e) {
1019 lyxerr << "Caught iconv exception: " << e.what() << endl;
1020 failed_export = true;
1022 catch (std::exception const & e) {
1023 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1024 failed_export = true;
1027 lyxerr << "Caught some really weird exception..." << endl;
1028 LyX::cref().emergencyCleanup();
1034 failed_export = true;
1035 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1038 if (failed_export) {
1039 Alert::error(_("Encoding error"),
1040 _("Some characters of your document are probably not "
1041 "representable in the chosen encoding.\n"
1042 "Changing the document encoding to utf8 could help."));
1049 void Buffer::writeLaTeXSource(odocstream & os,
1050 string const & original_path,
1051 OutputParams const & runparams_in,
1052 bool const output_preamble, bool const output_body)
1054 OutputParams runparams = runparams_in;
1056 // validate the buffer.
1057 LYXERR(Debug::LATEX) << " Validating buffer..." << endl;
1058 LaTeXFeatures features(*this, params(), runparams);
1060 LYXERR(Debug::LATEX) << " Buffer validation done." << endl;
1062 // The starting paragraph of the coming rows is the
1063 // first paragraph of the document. (Asger)
1064 if (output_preamble && runparams.nice) {
1065 os << "%% LyX " << lyx_version << " created this file. "
1066 "For more info, see http://www.lyx.org/.\n"
1067 "%% Do not edit unless you really know what "
1072 LYXERR(Debug::INFO) << "lyx document header finished" << endl;
1073 // There are a few differences between nice LaTeX and usual files:
1074 // usual is \batchmode and has a
1075 // special input@path to allow the including of figures
1076 // with either \input or \includegraphics (what figinsets do).
1077 // input@path is set when the actual parameter
1078 // original_path is set. This is done for usual tex-file, but not
1079 // for nice-latex-file. (Matthias 250696)
1080 // Note that input@path is only needed for something the user does
1081 // in the preamble, included .tex files or ERT, files included by
1082 // LyX work without it.
1083 if (output_preamble) {
1084 if (!runparams.nice) {
1085 // code for usual, NOT nice-latex-file
1086 os << "\\batchmode\n"; // changed
1087 // from \nonstopmode
1090 if (!original_path.empty()) {
1092 // We don't know the encoding of inputpath
1093 docstring const inputpath = from_utf8(latex_path(original_path));
1094 os << "\\makeatletter\n"
1095 << "\\def\\input@path{{"
1096 << inputpath << "/}}\n"
1097 << "\\makeatother\n";
1103 // Write the preamble
1104 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1110 os << "\\begin{document}\n";
1112 } // output_preamble
1114 texrow().start(paragraphs().begin()->id(), 0);
1116 LYXERR(Debug::INFO) << "preamble finished, now the body." << endl;
1118 if (!lyxrc.language_auto_begin &&
1119 !params().language->babel().empty()) {
1121 os << from_utf8(subst(lyxrc.language_command_begin,
1123 params().language->babel()))
1128 Encoding const & encoding = params().encoding();
1129 if (encoding.package() == Encoding::CJK) {
1130 // Open a CJK environment, since in contrast to the encodings
1131 // handled by inputenc the document encoding is not set in
1132 // the preamble if it is handled by CJK.sty.
1133 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1138 // if we are doing a real file with body, even if this is the
1139 // child of some other buffer, let's cut the link here.
1140 // This happens for example if only a child document is printed.
1141 string save_parentname;
1142 if (output_preamble) {
1143 save_parentname = params().parentname;
1144 params().parentname.erase();
1147 loadChildDocuments();
1150 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1152 // Restore the parenthood if needed
1153 if (output_preamble)
1154 params().parentname = save_parentname;
1156 // add this just in case after all the paragraphs
1160 if (encoding.package() == Encoding::CJK) {
1161 // Close the open CJK environment.
1162 // latexParagraphs will have opened one even if the last text
1164 os << "\\end{CJK}\n";
1168 if (!lyxrc.language_auto_end &&
1169 !params().language->babel().empty()) {
1170 os << from_utf8(subst(lyxrc.language_command_end,
1172 params().language->babel()))
1177 if (output_preamble) {
1178 os << "\\end{document}\n";
1181 LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl;
1183 LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made."
1186 runparams_in.encoding = runparams.encoding;
1188 // Just to be sure. (Asger)
1191 LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl;
1192 LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1
1197 bool Buffer::isLatex() const
1199 return params().getTextClass().outputType() == LATEX;
1203 bool Buffer::isLiterate() const
1205 return params().getTextClass().outputType() == LITERATE;
1209 bool Buffer::isDocBook() const
1211 return params().getTextClass().outputType() == DOCBOOK;
1215 void Buffer::makeDocBookFile(FileName const & fname,
1216 OutputParams const & runparams,
1217 bool const body_only)
1219 LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl;
1223 if (!openFileWrite(ofs, fname))
1226 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1230 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1234 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1235 OutputParams const & runparams,
1236 bool const only_body)
1238 LaTeXFeatures features(*this, params(), runparams);
1243 TextClass const & tclass = params().getTextClass();
1244 string const top_element = tclass.latexname();
1247 if (runparams.flavor == OutputParams::XML)
1248 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1251 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1254 if (! tclass.class_header().empty())
1255 os << from_ascii(tclass.class_header());
1256 else if (runparams.flavor == OutputParams::XML)
1257 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1258 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1260 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1262 docstring preamble = from_utf8(params().preamble);
1263 if (runparams.flavor != OutputParams::XML ) {
1264 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1265 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1266 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1267 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1270 string const name = runparams.nice
1271 ? changeExtension(absFileName(), ".sgml") : fname;
1272 preamble += features.getIncludedFiles(name);
1273 preamble += features.getLyXSGMLEntities();
1275 if (!preamble.empty()) {
1276 os << "\n [ " << preamble << " ]";
1281 string top = top_element;
1283 if (runparams.flavor == OutputParams::XML)
1284 top += params().language->code();
1286 top += params().language->code().substr(0,2);
1289 if (!params().options.empty()) {
1291 top += params().options;
1294 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1295 << " file was created by LyX " << lyx_version
1296 << "\n See http://www.lyx.org/ for more information -->\n";
1298 params().getTextClass().counters().reset();
1300 loadChildDocuments();
1302 sgml::openTag(os, top);
1304 docbookParagraphs(paragraphs(), *this, os, runparams);
1305 sgml::closeTag(os, top_element);
1309 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1310 // Other flags: -wall -v0 -x
1311 int Buffer::runChktex()
1315 // get LaTeX-Filename
1316 FileName const path(temppath());
1317 string const name = addName(path.absFilename(), latexName());
1318 string const org_path = filePath();
1320 support::Path p(path); // path to LaTeX file
1321 message(_("Running chktex..."));
1323 // Generate the LaTeX file if neccessary
1324 OutputParams runparams(¶ms().encoding());
1325 runparams.flavor = OutputParams::LATEX;
1326 runparams.nice = false;
1327 makeLaTeXFile(FileName(name), org_path, runparams);
1330 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1331 int const res = chktex.run(terr); // run chktex
1334 Alert::error(_("chktex failure"),
1335 _("Could not run chktex successfully."));
1336 } else if (res > 0) {
1337 ErrorList & errlist = pimpl_->errorLists["ChkTeX"];
1339 bufferErrors(terr, errlist);
1350 void Buffer::validate(LaTeXFeatures & features) const
1352 TextClass const & tclass = params().getTextClass();
1354 if (params().outputChanges) {
1355 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1356 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1357 LaTeXFeatures::isAvailable("xcolor");
1359 if (features.runparams().flavor == OutputParams::LATEX) {
1361 features.require("ct-dvipost");
1362 features.require("dvipost");
1363 } else if (xcolorsoul) {
1364 features.require("ct-xcolor-soul");
1365 features.require("soul");
1366 features.require("xcolor");
1368 features.require("ct-none");
1370 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1372 features.require("ct-xcolor-soul");
1373 features.require("soul");
1374 features.require("xcolor");
1375 features.require("pdfcolmk"); // improves color handling in PDF output
1377 features.require("ct-none");
1382 // AMS Style is at document level
1383 if (params().use_amsmath == BufferParams::package_on
1384 || tclass.provides("amsmath"))
1385 features.require("amsmath");
1386 if (params().use_esint == BufferParams::package_on)
1387 features.require("esint");
1389 loadChildDocuments();
1391 for_each(paragraphs().begin(), paragraphs().end(),
1392 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1394 // the bullet shapes are buffer level not paragraph level
1395 // so they are tested here
1396 for (int i = 0; i < 4; ++i) {
1397 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1398 int const font = params().user_defined_bullet(i).getFont();
1400 int const c = params()
1401 .user_defined_bullet(i)
1408 features.require("latexsym");
1410 } else if (font == 1) {
1411 features.require("amssymb");
1412 } else if ((font >= 2 && font <= 5)) {
1413 features.require("pifont");
1418 if (lyxerr.debugging(Debug::LATEX)) {
1419 features.showStruct();
1424 void Buffer::getLabelList(vector<docstring> & list) const
1426 /// if this is a child document and the parent is already loaded
1427 /// Use the parent's list instead [ale990407]
1428 Buffer const * tmp = masterBuffer();
1430 lyxerr << "masterBuffer() failed!" << endl;
1434 tmp->getLabelList(list);
1438 loadChildDocuments();
1440 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1441 it.nextInset()->getLabelList(*this, list);
1445 void Buffer::updateBibfilesCache()
1447 // if this is a child document and the parent is already loaded
1448 // update the parent's cache instead
1449 Buffer * tmp = masterBuffer();
1452 tmp->updateBibfilesCache();
1456 bibfilesCache_.clear();
1457 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1458 if (it->lyxCode() == BIBTEX_CODE) {
1459 InsetBibtex const & inset =
1460 static_cast<InsetBibtex const &>(*it);
1461 vector<FileName> const bibfiles = inset.getFiles(*this);
1462 bibfilesCache_.insert(bibfilesCache_.end(),
1465 } else if (it->lyxCode() == INCLUDE_CODE) {
1466 InsetInclude & inset =
1467 static_cast<InsetInclude &>(*it);
1468 inset.updateBibfilesCache(*this);
1469 vector<FileName> const & bibfiles =
1470 inset.getBibfilesCache(*this);
1471 bibfilesCache_.insert(bibfilesCache_.end(),
1479 vector<FileName> const & Buffer::getBibfilesCache() const
1481 // if this is a child document and the parent is already loaded
1482 // use the parent's cache instead
1483 Buffer const * tmp = masterBuffer();
1486 return tmp->getBibfilesCache();
1488 // We update the cache when first used instead of at loading time.
1489 if (bibfilesCache_.empty())
1490 const_cast<Buffer *>(this)->updateBibfilesCache();
1492 return bibfilesCache_;
1496 bool Buffer::isDepClean(string const & name) const
1498 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1499 if (it == pimpl_->dep_clean.end())
1505 void Buffer::markDepClean(string const & name)
1507 pimpl_->dep_clean[name] = true;
1511 bool Buffer::dispatch(string const & command, bool * result)
1513 return dispatch(lyxaction.lookupFunc(command), result);
1517 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1519 bool dispatched = true;
1521 switch (func.action) {
1522 case LFUN_BUFFER_EXPORT: {
1523 bool const tmp = doExport(to_utf8(func.argument()), false);
1536 void Buffer::changeLanguage(Language const * from, Language const * to)
1541 for_each(par_iterator_begin(),
1543 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1547 bool Buffer::isMultiLingual() const
1549 ParConstIterator end = par_iterator_end();
1550 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1551 if (it->isMultiLingual(params()))
1558 ParIterator Buffer::getParFromID(int const id) const
1560 ParConstIterator it = par_iterator_begin();
1561 ParConstIterator const end = par_iterator_end();
1564 // John says this is called with id == -1 from undo
1565 lyxerr << "getParFromID(), id: " << id << endl;
1569 for (; it != end; ++it)
1577 bool Buffer::hasParWithID(int const id) const
1579 ParConstIterator const it = getParFromID(id);
1580 return it != par_iterator_end();
1584 ParIterator Buffer::par_iterator_begin()
1586 return lyx::par_iterator_begin(inset());
1590 ParIterator Buffer::par_iterator_end()
1592 return lyx::par_iterator_end(inset());
1596 ParConstIterator Buffer::par_iterator_begin() const
1598 return lyx::par_const_iterator_begin(inset());
1602 ParConstIterator Buffer::par_iterator_end() const
1604 return lyx::par_const_iterator_end(inset());
1608 Language const * Buffer::language() const
1610 return params().language;
1614 docstring const Buffer::B_(string const & l10n) const
1616 return params().B_(l10n);
1620 bool Buffer::isClean() const
1622 return pimpl_->lyx_clean;
1626 bool Buffer::isBakClean() const
1628 return pimpl_->bak_clean;
1632 bool Buffer::isExternallyModified(CheckMethod method) const
1634 BOOST_ASSERT(pimpl_->filename.exists());
1635 // if method == timestamp, check timestamp before checksum
1636 return (method == checksum_method
1637 || pimpl_->timestamp_ != pimpl_->filename.lastModified())
1638 && pimpl_->checksum_ != sum(pimpl_->filename);
1642 void Buffer::saveCheckSum(FileName const & file) const
1644 if (file.exists()) {
1645 pimpl_->timestamp_ = file.lastModified();
1646 pimpl_->checksum_ = sum(file);
1648 // in the case of save to a new file.
1649 pimpl_->timestamp_ = 0;
1650 pimpl_->checksum_ = 0;
1655 void Buffer::markClean() const
1657 if (!pimpl_->lyx_clean) {
1658 pimpl_->lyx_clean = true;
1661 // if the .lyx file has been saved, we don't need an
1663 pimpl_->bak_clean = true;
1667 void Buffer::markBakClean() const
1669 pimpl_->bak_clean = true;
1673 void Buffer::setUnnamed(bool flag)
1675 pimpl_->unnamed = flag;
1679 bool Buffer::isUnnamed() const
1681 return pimpl_->unnamed;
1685 // FIXME: this function should be moved to buffer_pimpl.C
1686 void Buffer::markDirty()
1688 if (pimpl_->lyx_clean) {
1689 pimpl_->lyx_clean = false;
1692 pimpl_->bak_clean = false;
1694 DepClean::iterator it = pimpl_->dep_clean.begin();
1695 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1697 for (; it != end; ++it)
1702 string Buffer::absFileName() const
1704 return pimpl_->filename.absFilename();
1708 string const & Buffer::filePath() const
1710 return params().filepath;
1714 bool Buffer::isReadonly() const
1716 return pimpl_->read_only;
1720 void Buffer::setParentName(string const & name)
1722 if (name == pimpl_->filename.absFilename())
1723 // Avoids recursive include.
1724 params().parentname.clear();
1726 params().parentname = name;
1730 Buffer const * Buffer::masterBuffer() const
1732 if (!params().parentname.empty()
1733 && theBufferList().exists(params().parentname)) {
1734 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1735 //We need to check if the parent is us...
1736 //FIXME RECURSIVE INCLUDE
1737 //This is not sufficient, since recursive includes could be downstream.
1738 if (buf && buf != this)
1739 return buf->masterBuffer();
1746 Buffer * Buffer::masterBuffer()
1748 if (!params().parentname.empty()
1749 && theBufferList().exists(params().parentname)) {
1750 Buffer * buf = theBufferList().getBuffer(params().parentname);
1751 //We need to check if the parent is us...
1752 //FIXME RECURSIVE INCLUDE
1753 //This is not sufficient, since recursive includes could be downstream.
1754 if (buf && buf != this)
1755 return buf->masterBuffer();
1762 MacroData const & Buffer::getMacro(docstring const & name) const
1764 return pimpl_->macros.get(name);
1768 bool Buffer::hasMacro(docstring const & name) const
1770 return pimpl_->macros.has(name);
1774 void Buffer::insertMacro(docstring const & name, MacroData const & data)
1776 MacroTable::globalMacros().insert(name, data);
1777 pimpl_->macros.insert(name, data);
1781 void Buffer::buildMacros()
1783 // Start with global table.
1784 pimpl_->macros = MacroTable::globalMacros();
1787 ParagraphList const & pars = text().paragraphs();
1788 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1789 //lyxerr << "searching main par " << i
1790 // << " for macro definitions" << std::endl;
1791 InsetList const & insets = pars[i].insetList();
1792 InsetList::const_iterator it = insets.begin();
1793 InsetList::const_iterator end = insets.end();
1794 for ( ; it != end; ++it) {
1795 //lyxerr << "found inset code " << it->inset->lyxCode() << std::endl;
1796 if (it->inset->lyxCode() == MATHMACRO_CODE) {
1797 MathMacroTemplate const & mac
1798 = static_cast<MathMacroTemplate const &>(*it->inset);
1799 insertMacro(mac.name(), mac.asMacroData());
1806 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1809 //FIXME: This does not work for child documents yet.
1810 BOOST_ASSERT(code == CITE_CODE || code == REF_CODE);
1811 // Check if the label 'from' appears more than once
1812 vector<docstring> labels;
1814 if (code == CITE_CODE) {
1816 keys.fillWithBibKeys(this);
1817 BiblioInfo::const_iterator bit = keys.begin();
1818 BiblioInfo::const_iterator bend = keys.end();
1820 for (; bit != bend; ++bit)
1822 labels.push_back(bit->first);
1824 getLabelList(labels);
1826 if (std::count(labels.begin(), labels.end(), from) > 1)
1829 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1830 if (it->lyxCode() == code) {
1831 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1832 inset.replaceContents(to_utf8(from), to_utf8(to));
1838 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1839 pit_type par_end, bool full_source)
1841 OutputParams runparams(¶ms().encoding());
1842 runparams.nice = true;
1843 runparams.flavor = OutputParams::LATEX;
1844 runparams.linelen = lyxrc.plaintext_linelen;
1845 // No side effect of file copying and image conversion
1846 runparams.dryrun = true;
1850 os << "% " << _("Preview source code") << "\n\n";
1854 writeLaTeXSource(os, filePath(), runparams, true, true);
1856 writeDocBookSource(os, absFileName(), runparams, false);
1859 runparams.par_begin = par_begin;
1860 runparams.par_end = par_end;
1861 if (par_begin + 1 == par_end)
1863 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1867 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1868 convert<docstring>(par_begin),
1869 convert<docstring>(par_end - 1))
1873 // output paragraphs
1875 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1878 docbookParagraphs(paragraphs(), *this, os, runparams);
1884 ErrorList const & Buffer::errorList(string const & type) const
1886 static ErrorList const emptyErrorList;
1887 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1888 if (I == pimpl_->errorLists.end())
1889 return emptyErrorList;
1895 ErrorList & Buffer::errorList(string const & type)
1897 return pimpl_->errorLists[type];
1901 void Buffer::structureChanged() const
1904 gui_->structureChanged();
1908 void Buffer::embeddingChanged() const
1911 gui_->embeddingChanged();
1915 void Buffer::errors(std::string const & err) const
1922 void Buffer::message(docstring const & msg) const
1929 void Buffer::setBusy(bool on) const
1936 void Buffer::readonly(bool on) const
1943 void Buffer::updateTitles() const
1946 gui_->updateTitles();
1950 void Buffer::resetAutosaveTimers() const
1953 gui_->resetAutosaveTimers();
1957 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
1966 class AutoSaveBuffer : public support::ForkedProcess {
1969 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
1970 : buffer_(buffer), fname_(fname) {}
1972 virtual boost::shared_ptr<ForkedProcess> clone() const
1974 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
1979 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
1980 from_utf8(fname_.absFilename())));
1981 return run(DontWait);
1985 virtual int generateChild();
1987 Buffer const & buffer_;
1992 #if !defined (HAVE_FORK)
1996 int AutoSaveBuffer::generateChild()
1998 // tmp_ret will be located (usually) in /tmp
1999 // will that be a problem?
2000 pid_t const pid = fork();
2001 // If you want to debug the autosave
2002 // you should set pid to -1, and comment out the fork.
2003 if (pid == 0 || pid == -1) {
2004 // pid = -1 signifies that lyx was unable
2005 // to fork. But we will do the save
2007 bool failed = false;
2009 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2010 if (!tmp_ret.empty()) {
2011 buffer_.writeFile(tmp_ret);
2012 // assume successful write of tmp_ret
2013 if (!rename(tmp_ret, fname_)) {
2015 // most likely couldn't move between
2016 // filesystems unless write of tmp_ret
2017 // failed so remove tmp file (if it
2026 // failed to write/rename tmp_ret so try writing direct
2027 if (!buffer_.writeFile(fname_)) {
2028 // It is dangerous to do this in the child,
2029 // but safe in the parent, so...
2030 if (pid == -1) // emit message signal.
2031 buffer_.message(_("Autosave failed!"));
2034 if (pid == 0) { // we are the child so...
2044 // Perfect target for a thread...
2045 void Buffer::autoSave() const
2047 if (isBakClean() || isReadonly()) {
2048 // We don't save now, but we'll try again later
2049 resetAutosaveTimers();
2053 // emit message signal.
2054 message(_("Autosaving current document..."));
2056 // create autosave filename
2057 string fname = filePath();
2059 fname += onlyFilename(absFileName());
2062 AutoSaveBuffer autosave(*this, FileName(fname));
2066 resetAutosaveTimers();
2070 /** Write a buffer to a new file name and rename the buffer
2071 according to the new file name.
2073 This function is e.g. used by menu callbacks and
2074 LFUN_BUFFER_WRITE_AS.
2076 If 'newname' is empty (the default), the user is asked via a
2077 dialog for the buffer's new name and location.
2079 If 'newname' is non-empty and has an absolute path, that is used.
2080 Otherwise the base directory of the buffer is used as the base
2081 for any relative path in 'newname'.
2084 bool Buffer::writeAs(string const & newname)
2086 string fname = absFileName();
2087 string const oldname = fname;
2089 if (newname.empty()) { /// No argument? Ask user through dialog
2092 FileDialog fileDlg(_("Choose a filename to save document as"),
2093 LFUN_BUFFER_WRITE_AS,
2094 make_pair(_("Documents|#o#O"),
2095 from_utf8(lyxrc.document_path)),
2096 make_pair(_("Templates|#T#t"),
2097 from_utf8(lyxrc.template_path)));
2099 if (!support::isLyXFilename(fname))
2102 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2104 FileDialog::Result result =
2105 fileDlg.save(from_utf8(onlyPath(fname)),
2107 from_utf8(onlyFilename(fname)));
2109 if (result.first == FileDialog::Later)
2112 fname = to_utf8(result.second);
2117 // Make sure the absolute filename ends with appropriate suffix
2118 fname = makeAbsPath(fname).absFilename();
2119 if (!support::isLyXFilename(fname))
2123 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2125 if (FileName(fname).exists()) {
2126 docstring const file = makeDisplayPath(fname, 30);
2127 docstring text = bformat(_("The document %1$s already "
2128 "exists.\n\nDo you want to "
2129 "overwrite that document?"),
2131 int const ret = Alert::prompt(_("Overwrite document?"),
2132 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2138 // Ok, change the name of the buffer
2141 bool unnamed = isUnnamed();
2143 saveCheckSum(FileName(fname));
2146 setFileName(oldname);
2147 setUnnamed(unnamed);
2148 saveCheckSum(FileName(oldname));
2152 removeAutosaveFile(oldname);
2157 bool Buffer::menuWrite()
2160 LyX::ref().session().lastFiles().add(FileName(absFileName()));
2164 // FIXME: we don't tell the user *WHY* the save failed !!
2166 docstring const file = makeDisplayPath(absFileName(), 30);
2168 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2169 "Do you want to rename the document and "
2170 "try again?"), file);
2171 int const ret = Alert::prompt(_("Rename and save?"),
2172 text, 0, 1, _("&Rename"), _("&Cancel"));
2181 void Buffer::loadChildDocuments() const
2183 bool parse_error = false;
2185 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2186 if (it->lyxCode() != INCLUDE_CODE)
2188 InsetInclude const & inset = static_cast<InsetInclude const &>(*it);
2189 InsetCommandParams const & ip = inset.params();
2190 Buffer * child = loadIfNeeded(*this, ip);
2193 parse_error |= !child->errorList("Parse").empty();
2194 child->loadChildDocuments();
2197 if (use_gui && masterBuffer() == this)
2198 updateLabels(*this);
2202 string Buffer::bufferFormat() const
2212 bool Buffer::doExport(string const & format,
2213 bool put_in_tempdir, string & result_file)
2215 string backend_format;
2216 OutputParams runparams(¶ms().encoding());
2217 runparams.flavor = OutputParams::LATEX;
2218 runparams.linelen = lyxrc.plaintext_linelen;
2219 vector<string> backs = backends();
2220 if (find(backs.begin(), backs.end(), format) == backs.end()) {
2221 // Get shortest path to format
2222 Graph::EdgePath path;
2223 for (vector<string>::const_iterator it = backs.begin();
2224 it != backs.end(); ++it) {
2225 Graph::EdgePath p = theConverters().getPath(*it, format);
2226 if (!p.empty() && (path.empty() || p.size() < path.size())) {
2227 backend_format = *it;
2232 runparams.flavor = theConverters().getFlavor(path);
2234 Alert::error(_("Couldn't export file"),
2235 bformat(_("No information for exporting the format %1$s."),
2236 formats.prettyName(format)));
2240 backend_format = format;
2241 // FIXME: Don't hardcode format names here, but use a flag
2242 if (backend_format == "pdflatex")
2243 runparams.flavor = OutputParams::PDFLATEX;
2246 string filename = latexName(false);
2247 filename = addName(temppath(), filename);
2248 filename = changeExtension(filename,
2249 formats.extension(backend_format));
2251 // Plain text backend
2252 if (backend_format == "text")
2253 writePlaintextFile(*this, FileName(filename), runparams);
2255 else if (backend_format == "lyx")
2256 writeFile(FileName(filename));
2258 else if (isDocBook()) {
2259 runparams.nice = !put_in_tempdir;
2260 makeDocBookFile(FileName(filename), runparams);
2263 else if (backend_format == format) {
2264 runparams.nice = true;
2265 if (!makeLaTeXFile(FileName(filename), string(), runparams))
2267 } else if (!lyxrc.tex_allows_spaces
2268 && support::contains(filePath(), ' ')) {
2269 Alert::error(_("File name error"),
2270 _("The directory path to the document cannot contain spaces."));
2273 runparams.nice = false;
2274 if (!makeLaTeXFile(FileName(filename), filePath(), runparams))
2278 string const error_type = (format == "program")
2279 ? "Build" : bufferFormat();
2280 string const ext = formats.extension(format);
2281 FileName const tmp_result_file(changeExtension(filename, ext));
2282 bool const success = theConverters().convert(this, FileName(filename),
2283 tmp_result_file, FileName(absFileName()), backend_format, format,
2284 errorList(error_type));
2285 // Emit the signal to show the error list.
2286 if (format != backend_format)
2292 result_file = tmp_result_file.absFilename();
2294 result_file = changeExtension(absFileName(), ext);
2295 // We need to copy referenced files (e. g. included graphics
2296 // if format == "dvi") to the result dir.
2297 vector<ExportedFile> const files =
2298 runparams.exportdata->externalFiles(format);
2299 string const dest = onlyPath(result_file);
2300 CopyStatus status = SUCCESS;
2301 for (vector<ExportedFile>::const_iterator it = files.begin();
2302 it != files.end() && status != CANCEL; ++it) {
2304 formats.getFormatFromFile(it->sourceName);
2305 status = copyFile(fmt, it->sourceName,
2306 makeAbsPath(it->exportName, dest),
2307 it->exportName, status == FORCE);
2309 if (status == CANCEL) {
2310 message(_("Document export cancelled."));
2311 } else if (tmp_result_file.exists()) {
2312 // Finally copy the main file
2313 status = copyFile(format, tmp_result_file,
2314 FileName(result_file), result_file,
2316 message(bformat(_("Document exported as %1$s "
2318 formats.prettyName(format),
2319 makeDisplayPath(result_file)));
2321 // This must be a dummy converter like fax (bug 1888)
2322 message(bformat(_("Document exported as %1$s"),
2323 formats.prettyName(format)));
2331 bool Buffer::doExport(string const & format, bool put_in_tempdir)
2334 return doExport(format, put_in_tempdir, result_file);
2338 bool Buffer::preview(string const & format)
2341 if (!doExport(format, true, result_file))
2343 return formats.view(*this, FileName(result_file), format);
2347 bool Buffer::isExportable(string const & format) const
2349 vector<string> backs = backends();
2350 for (vector<string>::const_iterator it = backs.begin();
2351 it != backs.end(); ++it)
2352 if (theConverters().isReachable(*it, format))
2358 vector<Format const *> Buffer::exportableFormats(bool only_viewable) const
2360 vector<string> backs = backends();
2361 vector<Format const *> result =
2362 theConverters().getReachable(backs[0], only_viewable, true);
2363 for (vector<string>::const_iterator it = backs.begin() + 1;
2364 it != backs.end(); ++it) {
2365 vector<Format const *> r =
2366 theConverters().getReachable(*it, only_viewable, false);
2367 result.insert(result.end(), r.begin(), r.end());
2373 vector<string> Buffer::backends() const
2376 if (params().getTextClass().isTeXClassAvailable()) {
2377 v.push_back(bufferFormat());
2378 // FIXME: Don't hardcode format names here, but use a flag
2379 if (v.back() == "latex")
2380 v.push_back("pdflatex");
2382 v.push_back("text");
2388 bool Buffer::readFileHelper(FileName const & s)
2390 // File information about normal file
2392 docstring const file = makeDisplayPath(s.absFilename(), 50);
2393 docstring text = bformat(_("The specified document\n%1$s"
2394 "\ncould not be read."), file);
2395 Alert::error(_("Could not read document"), text);
2399 // Check if emergency save file exists and is newer.
2400 FileName const e(s.absFilename() + ".emergency");
2402 if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) {
2403 docstring const file = makeDisplayPath(s.absFilename(), 20);
2404 docstring const text =
2405 bformat(_("An emergency save of the document "
2407 "Recover emergency save?"), file);
2408 switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
2409 _("&Recover"), _("&Load Original"),
2413 // the file is not saved if we load the emergency file.
2423 // Now check if autosave file is newer.
2424 FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#');
2426 if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) {
2427 docstring const file = makeDisplayPath(s.absFilename(), 20);
2428 docstring const text =
2429 bformat(_("The backup of the document "
2430 "%1$s is newer.\n\nLoad the "
2431 "backup instead?"), file);
2432 switch (Alert::prompt(_("Load backup?"), text, 0, 2,
2433 _("&Load backup"), _("Load &original"),
2437 // the file is not saved if we load the autosave file.
2441 // Here we delete the autosave
2452 bool Buffer::loadLyXFile(FileName const & s)
2454 if (s.isReadable()) {
2455 if (readFileHelper(s)) {
2456 lyxvc().file_found_hook(s);
2457 if (!s.isWritable())
2462 docstring const file = makeDisplayPath(s.absFilename(), 20);
2463 // Here we probably should run
2464 if (LyXVC::file_not_found_hook(s)) {
2465 docstring const text =
2466 bformat(_("Do you want to retrieve the document"
2467 " %1$s from version control?"), file);
2468 int const ret = Alert::prompt(_("Retrieve from version control?"),
2469 text, 0, 1, _("&Retrieve"), _("&Cancel"));
2472 // How can we know _how_ to do the checkout?
2473 // With the current VC support it has to be,
2474 // a RCS file since CVS do not have special ,v files.
2476 return loadLyXFile(s);
2484 void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
2486 TeXErrors::Errors::const_iterator cit = terr.begin();
2487 TeXErrors::Errors::const_iterator end = terr.end();
2489 for (; cit != end; ++cit) {
2492 int errorRow = cit->error_in_line;
2493 bool found = texrow().getIdFromRow(errorRow, id_start,
2499 found = texrow().getIdFromRow(errorRow, id_end, pos_end);
2500 } while (found && id_start == id_end && pos_start == pos_end);
2502 errorList.push_back(ErrorItem(cit->error_desc,
2503 cit->error_text, id_start, pos_start, pos_end));