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"
25 #include "DocIterator.h"
27 #include "ErrorList.h"
30 #include "FuncRequest.h"
32 #include "InsetIterator.h"
35 #include "LaTeXFeatures.h"
37 #include "LyXAction.h"
45 #include "output_docbook.h"
46 #include "output_latex.h"
47 #include "Paragraph.h"
48 #include "paragraph_funcs.h"
49 #include "ParagraphParameters.h"
50 #include "ParIterator.h"
54 #include "TextClassList.h"
55 #include "TexStream.h"
56 #include "TocBackend.h"
59 #include "EmbeddedFiles.h"
60 #include "PDFOptions.h"
62 #include "insets/InsetBibitem.h"
63 #include "insets/InsetBibtex.h"
64 #include "insets/InsetInclude.h"
65 #include "insets/InsetText.h"
67 #include "mathed/MathMacroTemplate.h"
68 #include "mathed/MacroTable.h"
69 #include "mathed/MathSupport.h"
71 #include "frontends/alert.h"
72 #include "frontends/Delegates.h"
73 #include "frontends/WorkAreaManager.h"
74 #include "frontends/FileDialog.h"
76 #include "graphics/Previews.h"
78 #include "support/types.h"
79 #include "support/lyxalgo.h"
80 #include "support/FileFilterList.h"
81 #include "support/filetools.h"
82 #include "support/Forkedcall.h"
83 #include "support/fs_extras.h"
84 #include "support/gzstream.h"
85 #include "support/lyxlib.h"
86 #include "support/os.h"
87 #include "support/Path.h"
88 #include "support/textutils.h"
89 #include "support/convert.h"
91 #if !defined (HAVE_FORK)
95 #include <boost/bind.hpp>
96 #include <boost/filesystem/exception.hpp>
97 #include <boost/filesystem/operations.hpp>
98 #include <boost/shared_ptr.hpp>
108 using std::make_pair;
113 using std::ostringstream;
124 using support::addName;
125 using support::bformat;
126 using support::changeExtension;
127 using support::cmd_ret;
128 using support::createBufferTmpDir;
129 using support::destroyDir;
130 using support::FileName;
131 using support::getFormatFromContents;
132 using support::libFileSearch;
133 using support::latex_path;
134 using support::ltrim;
135 using support::makeAbsPath;
136 using support::makeDisplayPath;
137 using support::makeLatexName;
138 using support::onlyFilename;
139 using support::onlyPath;
140 using support::quoteName;
141 using support::removeAutosaveFile;
142 using support::rename;
143 using support::runCommand;
144 using support::split;
145 using support::subst;
146 using support::tempName;
149 using support::suffixIs;
151 namespace Alert = frontend::Alert;
152 namespace os = support::os;
153 namespace fs = boost::filesystem;
157 int const LYX_FORMAT = 293;
162 typedef std::map<string, bool> DepClean;
167 Impl(Buffer & parent, FileName const & file, bool readonly);
169 limited_stack<Undo> undostack;
170 limited_stack<Undo> redostack;
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_;
225 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
226 : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
227 filename(file), file_fully_loaded(false), inset(params),
228 toc_backend(&parent), embedded_files(&parent), timestamp_(0),
231 inset.setAutoBreakRows(true);
232 lyxvc.buffer(&parent);
233 temppath = createBufferTmpDir();
234 params.filepath = onlyPath(file.absFilename());
235 // FIXME: And now do something if temppath == string(), because we
236 // assume from now on that temppath points to a valid temp dir.
237 // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html
240 wa_ = new frontend::WorkAreaManager;
244 Buffer::Buffer(string const & file, bool readonly)
245 : pimpl_(new Impl(*this, FileName(file), readonly)), gui_(0)
247 LYXERR(Debug::INFO) << "Buffer::Buffer()" << endl;
253 LYXERR(Debug::INFO) << "Buffer::~Buffer()" << endl;
254 // here the buffer should take care that it is
255 // saved properly, before it goes into the void.
257 Buffer * master = getMasterBuffer();
258 if (master != this && use_gui)
259 // We are closing buf which was a child document so we
260 // must update the labels and section numbering of its master
262 updateLabels(*master);
264 if (!temppath().empty() && !destroyDir(FileName(temppath()))) {
265 Alert::warning(_("Could not remove temporary directory"),
266 bformat(_("Could not remove the temporary directory %1$s"),
267 from_utf8(temppath())));
270 // Remove any previewed LaTeX snippets associated with this buffer.
271 graphics::Previews::get().removeLoader(*this);
274 pimpl_->wa_->closeAll();
281 void Buffer::changed() const
284 pimpl_->wa_->redrawAll();
288 frontend::WorkAreaManager & Buffer::workAreaManager() const
290 BOOST_ASSERT(pimpl_->wa_);
295 Text & Buffer::text() const
297 return const_cast<Text &>(pimpl_->inset.text_);
301 Inset & Buffer::inset() const
303 return const_cast<InsetText &>(pimpl_->inset);
307 limited_stack<Undo> & Buffer::undostack()
309 return pimpl_->undostack;
313 limited_stack<Undo> const & Buffer::undostack() const
315 return pimpl_->undostack;
319 limited_stack<Undo> & Buffer::redostack()
321 return pimpl_->redostack;
325 limited_stack<Undo> const & Buffer::redostack() const
327 return pimpl_->redostack;
331 BufferParams & Buffer::params()
333 return pimpl_->params;
337 BufferParams const & Buffer::params() const
339 return pimpl_->params;
343 ParagraphList & Buffer::paragraphs()
345 return text().paragraphs();
349 ParagraphList const & Buffer::paragraphs() const
351 return text().paragraphs();
355 LyXVC & Buffer::lyxvc()
357 return pimpl_->lyxvc;
361 LyXVC const & Buffer::lyxvc() const
363 return pimpl_->lyxvc;
367 string const & Buffer::temppath() const
369 return pimpl_->temppath;
373 TexRow & Buffer::texrow()
375 return pimpl_->texrow;
379 TexRow const & Buffer::texrow() const
381 return pimpl_->texrow;
385 TocBackend & Buffer::tocBackend()
387 return pimpl_->toc_backend;
391 TocBackend const & Buffer::tocBackend() const
393 return pimpl_->toc_backend;
397 EmbeddedFiles & Buffer::embeddedFiles()
399 return pimpl_->embedded_files;
403 EmbeddedFiles const & Buffer::embeddedFiles() const
405 return pimpl_->embedded_files;
409 string const Buffer::getLatexName(bool const no_path) const
411 string const name = changeExtension(makeLatexName(fileName()), ".tex");
412 return no_path ? onlyFilename(name) : name;
416 pair<Buffer::LogType, string> const Buffer::getLogName() const
418 string const filename = getLatexName(false);
420 if (filename.empty())
421 return make_pair(Buffer::latexlog, string());
423 string const path = temppath();
425 FileName const fname(addName(temppath(),
426 onlyFilename(changeExtension(filename,
428 FileName const bname(
429 addName(path, onlyFilename(
430 changeExtension(filename,
431 formats.extension("literate") + ".out"))));
433 // If no Latex log or Build log is newer, show Build log
435 if (fs::exists(bname.toFilesystemEncoding()) &&
436 (!fs::exists(fname.toFilesystemEncoding()) ||
437 fs::last_write_time(fname.toFilesystemEncoding()) < fs::last_write_time(bname.toFilesystemEncoding()))) {
438 LYXERR(Debug::FILES) << "Log name calculated as: " << bname << endl;
439 return make_pair(Buffer::buildlog, bname.absFilename());
441 LYXERR(Debug::FILES) << "Log name calculated as: " << fname << endl;
442 return make_pair(Buffer::latexlog, fname.absFilename());
446 void Buffer::setReadonly(bool const flag)
448 if (pimpl_->read_only != flag) {
449 pimpl_->read_only = flag;
455 void Buffer::setFileName(string const & newfile)
457 pimpl_->filename = makeAbsPath(newfile);
458 params().filepath = onlyPath(pimpl_->filename.absFilename());
459 setReadonly(fs::is_readonly(pimpl_->filename.toFilesystemEncoding()));
464 // We'll remove this later. (Lgb)
467 void unknownClass(string const & unknown)
469 Alert::warning(_("Unknown document class"),
470 bformat(_("Using the default document class, because the "
471 "class %1$s is unknown."), from_utf8(unknown)));
477 int Buffer::readHeader(Lexer & lex)
479 int unknown_tokens = 0;
481 int begin_header_line = -1;
483 // Initialize parameters that may be/go lacking in header:
484 params().branchlist().clear();
485 params().preamble.erase();
486 params().options.erase();
487 params().float_placement.erase();
488 params().paperwidth.erase();
489 params().paperheight.erase();
490 params().leftmargin.erase();
491 params().rightmargin.erase();
492 params().topmargin.erase();
493 params().bottommargin.erase();
494 params().headheight.erase();
495 params().headsep.erase();
496 params().footskip.erase();
497 params().listings_params.clear();
498 params().clearLayoutModules();
499 params().pdfoptions().clear();
501 for (int i = 0; i < 4; ++i) {
502 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
503 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
506 ErrorList & errorList = pimpl_->errorLists["Parse"];
510 string const token = lex.getString();
515 if (token == "\\end_header")
519 if (token == "\\begin_header") {
520 begin_header_line = line;
524 LYXERR(Debug::PARSER) << "Handling document header token: `"
525 << token << '\'' << endl;
527 string unknown = params().readToken(lex, token);
528 if (!unknown.empty()) {
529 if (unknown[0] != '\\' && token == "\\textclass") {
530 unknownClass(unknown);
533 docstring const s = bformat(_("Unknown token: "
537 errorList.push_back(ErrorItem(_("Document header error"),
542 if (begin_header_line) {
543 docstring const s = _("\\begin_header is missing");
544 errorList.push_back(ErrorItem(_("Document header error"),
548 return unknown_tokens;
553 // changed to be public and have one parameter
554 // Returns false if "\end_document" is not read (Asger)
555 bool Buffer::readDocument(Lexer & lex)
557 ErrorList & errorList = pimpl_->errorLists["Parse"];
561 string const token = lex.getString();
562 if (token != "\\begin_document") {
563 docstring const s = _("\\begin_document is missing");
564 errorList.push_back(ErrorItem(_("Document header error"),
568 // we are reading in a brand new document
569 BOOST_ASSERT(paragraphs().empty());
572 TextClass const & baseClass = textclasslist[params().getBaseClass()];
573 if (!baseClass.load(filePath())) {
574 string theclass = baseClass.name();
575 Alert::error(_("Can't load document class"), bformat(
576 _("Using the default document class, because the "
577 "class %1$s could not be loaded."), from_utf8(theclass)));
578 params().setBaseClass(defaultTextclass());
581 if (params().outputChanges) {
582 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
583 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
584 LaTeXFeatures::isAvailable("xcolor");
586 if (!dvipost && !xcolorsoul) {
587 Alert::warning(_("Changes not shown in LaTeX output"),
588 _("Changes will not be highlighted in LaTeX output, "
589 "because neither dvipost nor xcolor/soul are installed.\n"
590 "Please install these packages or redefine "
591 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
592 } else if (!xcolorsoul) {
593 Alert::warning(_("Changes not shown in LaTeX output"),
594 _("Changes will not be highlighted in LaTeX output "
595 "when using pdflatex, because xcolor and soul are not installed.\n"
596 "Please install both packages or redefine "
597 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
600 // read manifest after header
601 embeddedFiles().readManifest(lex, errorList);
604 bool const res = text().read(*this, lex, errorList);
605 for_each(text().paragraphs().begin(),
606 text().paragraphs().end(),
607 bind(&Paragraph::setInsetOwner, _1, &inset()));
613 // needed to insert the selection
614 void Buffer::insertStringAsLines(ParagraphList & pars,
615 pit_type & pit, pos_type & pos,
616 Font const & fn, docstring const & str, bool autobreakrows)
620 // insert the string, don't insert doublespace
621 bool space_inserted = true;
622 for (docstring::const_iterator cit = str.begin();
623 cit != str.end(); ++cit) {
624 Paragraph & par = pars[pit];
626 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
627 breakParagraph(params(), pars, pit, pos,
628 par.layout()->isEnvironment());
631 space_inserted = true;
635 // do not insert consecutive spaces if !free_spacing
636 } else if ((*cit == ' ' || *cit == '\t') &&
637 space_inserted && !par.isFreeSpacing()) {
639 } else if (*cit == '\t') {
640 if (!par.isFreeSpacing()) {
641 // tabs are like spaces here
642 par.insertChar(pos, ' ', font, params().trackChanges);
644 space_inserted = true;
646 const pos_type n = 8 - pos % 8;
647 for (pos_type i = 0; i < n; ++i) {
648 par.insertChar(pos, ' ', font, params().trackChanges);
651 space_inserted = true;
653 } else if (!isPrintable(*cit)) {
654 // Ignore unprintables
657 // just insert the character
658 par.insertChar(pos, *cit, font, params().trackChanges);
660 space_inserted = (*cit == ' ');
667 bool Buffer::readString(std::string const & s)
669 params().compressed = false;
671 // remove dummy empty par
672 paragraphs().clear();
674 std::istringstream is(s);
676 FileName const name(tempName());
677 switch (readFile(lex, name, true)) {
681 // We need to call lyx2lyx, so write the input to a file
682 std::ofstream os(name.toFilesystemEncoding().c_str());
685 return readFile(name);
695 bool Buffer::readFile(FileName const & filename)
697 FileName fname(filename);
698 // Check if the file is compressed.
699 string format = getFormatFromContents(filename);
700 if (format == "zip") {
701 // decompress to a temp directory
702 LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl;
703 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
705 FileName lyxfile(addName(temppath(), "content.lyx"));
706 // if both manifest.txt and file.lyx exist, this is am embedded file
707 if (fs::exists(lyxfile.toFilesystemEncoding())) {
708 params().embedded = true;
712 // The embedded lyx file can also be compressed, for backward compatibility
713 format = getFormatFromContents(fname);
714 if (format == "gzip" || format == "zip" || format == "compress") {
715 params().compressed = true;
718 // remove dummy empty par
719 paragraphs().clear();
722 if (readFile(lex, fname) != success)
729 bool Buffer::fully_loaded() const
731 return pimpl_->file_fully_loaded;
735 void Buffer::fully_loaded(bool const value)
737 pimpl_->file_fully_loaded = value;
741 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
744 BOOST_ASSERT(!filename.empty());
747 Alert::error(_("Document could not be read"),
748 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
753 string const token(lex.getString());
756 Alert::error(_("Document could not be read"),
757 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
761 // the first token _must_ be...
762 if (token != "\\lyxformat") {
763 lyxerr << "Token: " << token << endl;
765 Alert::error(_("Document format failure"),
766 bformat(_("%1$s is not a LyX document."),
767 from_utf8(filename.absFilename())));
772 string tmp_format = lex.getString();
773 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
774 // if present remove ".," from string.
775 string::size_type dot = tmp_format.find_first_of(".,");
776 //lyxerr << " dot found at " << dot << endl;
777 if (dot != string::npos)
778 tmp_format.erase(dot, 1);
779 int const file_format = convert<int>(tmp_format);
780 //lyxerr << "format: " << file_format << endl;
782 // save timestamp and checksum of the original disk file, making sure
783 // to not overwrite them with those of the file created in the tempdir
784 // when it has to be converted to the current format.
785 if (!pimpl_->checksum_) {
786 // Save the timestamp and checksum of disk file. If filename is an
787 // emergency file, save the timestamp and sum of the original lyx file
788 // because isExternallyModified will check for this file. (BUG4193)
789 string diskfile = filename.toFilesystemEncoding();
790 if (suffixIs(diskfile, ".emergency"))
791 diskfile = diskfile.substr(0, diskfile.size() - 10);
792 saveCheckSum(diskfile);
795 if (file_format != LYX_FORMAT) {
798 // lyx2lyx would fail
801 FileName const tmpfile(tempName());
802 if (tmpfile.empty()) {
803 Alert::error(_("Conversion failed"),
804 bformat(_("%1$s is from a different"
805 " version of LyX, but a temporary"
806 " file for converting it could"
808 from_utf8(filename.absFilename())));
811 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
812 if (lyx2lyx.empty()) {
813 Alert::error(_("Conversion script not found"),
814 bformat(_("%1$s is from a different"
815 " version of LyX, but the"
816 " conversion script lyx2lyx"
817 " could not be found."),
818 from_utf8(filename.absFilename())));
821 ostringstream command;
822 command << os::python()
823 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
824 << " -t " << convert<string>(LYX_FORMAT)
825 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
826 << ' ' << quoteName(filename.toFilesystemEncoding());
827 string const command_str = command.str();
829 LYXERR(Debug::INFO) << "Running '"
830 << command_str << '\''
833 cmd_ret const ret = runCommand(command_str);
834 if (ret.first != 0) {
835 Alert::error(_("Conversion script failed"),
836 bformat(_("%1$s is from a different version"
837 " of LyX, but the lyx2lyx script"
838 " failed to convert it."),
839 from_utf8(filename.absFilename())));
842 bool const ret = readFile(tmpfile);
843 // Do stuff with tmpfile name and buffer name here.
844 return ret ? success : failure;
849 if (readDocument(lex)) {
850 Alert::error(_("Document format failure"),
851 bformat(_("%1$s ended unexpectedly, which means"
852 " that it is probably corrupted."),
853 from_utf8(filename.absFilename())));
856 //lyxerr << "removing " << MacroTable::localMacros().size()
857 // << " temporary macro entries" << endl;
858 //MacroTable::localMacros().clear();
860 pimpl_->file_fully_loaded = true;
865 // Should probably be moved to somewhere else: BufferView? LyXView?
866 bool Buffer::save() const
868 // We don't need autosaves in the immediate future. (Asger)
869 resetAutosaveTimers();
871 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
874 bool madeBackup = false;
876 // make a backup if the file already exists
877 if (lyxrc.make_backup && fs::exists(encodedFilename)) {
878 backupName = FileName(fileName() + '~');
879 if (!lyxrc.backupdir_path.empty())
880 backupName = FileName(addName(lyxrc.backupdir_path,
881 subst(os::internal_path(backupName.absFilename()), '/', '!')));
884 fs::copy_file(encodedFilename, backupName.toFilesystemEncoding(), false);
886 } catch (fs::filesystem_error const & fe) {
887 Alert::error(_("Backup failure"),
888 bformat(_("Cannot create backup file %1$s.\n"
889 "Please check whether the directory exists and is writeable."),
890 from_utf8(backupName.absFilename())));
891 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
895 // ask if the disk file has been externally modified (use checksum method)
896 if (fs::exists(encodedFilename) && isExternallyModified(checksum_method)) {
897 docstring const file = makeDisplayPath(fileName(), 20);
898 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
899 "you want to overwrite this file?"), file);
900 int const ret = Alert::prompt(_("Overwrite modified file?"),
901 text, 1, 1, _("&Overwrite"), _("&Cancel"));
906 if (writeFile(pimpl_->filename)) {
908 removeAutosaveFile(fileName());
909 saveCheckSum(pimpl_->filename.toFilesystemEncoding());
912 // Saving failed, so backup is not backup
914 rename(backupName, pimpl_->filename);
920 bool Buffer::writeFile(FileName const & fname) const
922 if (pimpl_->read_only && fname == pimpl_->filename)
928 if (params().embedded)
929 // first write the .lyx file to the temporary directory
930 content = FileName(addName(temppath(), "content.lyx"));
934 if (params().compressed) {
935 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
941 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
948 if (retval && params().embedded) {
949 // write file.lyx and all the embedded files to the zip file fname
950 // if embedding is enabled
951 return pimpl_->embedded_files.writeFile(fname);
957 bool Buffer::write(ostream & ofs) const
960 // Use the standard "C" locale for file output.
961 ofs.imbue(std::locale::classic());
964 // The top of the file should not be written by params().
966 // write out a comment in the top of the file
967 ofs << "#LyX " << lyx_version
968 << " created this file. For more info see http://www.lyx.org/\n"
969 << "\\lyxformat " << LYX_FORMAT << "\n"
970 << "\\begin_document\n";
973 /// For each author, set 'used' to true if there is a change
974 /// by this author in the document; otherwise set it to 'false'.
975 AuthorList::Authors::const_iterator a_it = params().authors().begin();
976 AuthorList::Authors::const_iterator a_end = params().authors().end();
977 for (; a_it != a_end; ++a_it)
978 a_it->second.used(false);
980 ParIterator const end = par_iterator_end();
981 ParIterator it = par_iterator_begin();
982 for ( ; it != end; ++it)
983 it->checkAuthors(params().authors());
985 // now write out the buffer parameters.
986 ofs << "\\begin_header\n";
987 params().writeFile(ofs);
988 ofs << "\\end_header\n";
990 // write the manifest after header
991 ofs << "\n\\begin_manifest\n";
992 pimpl_->embedded_files.update();
993 embeddedFiles().writeManifest(ofs);
994 ofs << "\\end_manifest\n";
997 ofs << "\n\\begin_body\n";
998 text().write(*this, ofs);
999 ofs << "\n\\end_body\n";
1001 // Write marker that shows file is complete
1002 ofs << "\\end_document" << endl;
1004 // Shouldn't really be needed....
1007 // how to check if close went ok?
1008 // Following is an attempt... (BE 20001011)
1010 // good() returns false if any error occured, including some
1011 // formatting error.
1012 // bad() returns true if something bad happened in the buffer,
1013 // which should include file system full errors.
1018 lyxerr << "File was not closed properly." << endl;
1025 bool Buffer::makeLaTeXFile(FileName const & fname,
1026 string const & original_path,
1027 OutputParams const & runparams,
1028 bool output_preamble, bool output_body)
1030 string const encoding = runparams.encoding->iconvName();
1031 LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: "
1032 << encoding << "..." << endl;
1034 odocfstream ofs(encoding);
1035 if (!openFileWrite(ofs, fname))
1038 //TexStream ts(ofs.rdbuf(), &texrow());
1040 bool failed_export = false;
1043 writeLaTeXSource(ofs, original_path,
1044 runparams, output_preamble, output_body);
1046 catch (iconv_codecvt_facet_exception & e) {
1047 lyxerr << "Caught iconv exception: " << e.what() << endl;
1048 failed_export = true;
1050 catch (std::exception const & e) {
1051 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1052 failed_export = true;
1055 lyxerr << "Caught some really weird exception..." << endl;
1056 LyX::cref().emergencyCleanup();
1062 failed_export = true;
1063 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1066 if (failed_export) {
1067 Alert::error(_("Encoding error"),
1068 _("Some characters of your document are probably not "
1069 "representable in the chosen encoding.\n"
1070 "Changing the document encoding to utf8 could help."));
1077 void Buffer::writeLaTeXSource(odocstream & os,
1078 string const & original_path,
1079 OutputParams const & runparams_in,
1080 bool const output_preamble, bool const output_body)
1082 OutputParams runparams = runparams_in;
1084 // validate the buffer.
1085 LYXERR(Debug::LATEX) << " Validating buffer..." << endl;
1086 LaTeXFeatures features(*this, params(), runparams);
1088 LYXERR(Debug::LATEX) << " Buffer validation done." << endl;
1090 // The starting paragraph of the coming rows is the
1091 // first paragraph of the document. (Asger)
1092 if (output_preamble && runparams.nice) {
1093 os << "%% LyX " << lyx_version << " created this file. "
1094 "For more info, see http://www.lyx.org/.\n"
1095 "%% Do not edit unless you really know what "
1100 LYXERR(Debug::INFO) << "lyx document header finished" << endl;
1101 // There are a few differences between nice LaTeX and usual files:
1102 // usual is \batchmode and has a
1103 // special input@path to allow the including of figures
1104 // with either \input or \includegraphics (what figinsets do).
1105 // input@path is set when the actual parameter
1106 // original_path is set. This is done for usual tex-file, but not
1107 // for nice-latex-file. (Matthias 250696)
1108 // Note that input@path is only needed for something the user does
1109 // in the preamble, included .tex files or ERT, files included by
1110 // LyX work without it.
1111 if (output_preamble) {
1112 if (!runparams.nice) {
1113 // code for usual, NOT nice-latex-file
1114 os << "\\batchmode\n"; // changed
1115 // from \nonstopmode
1118 if (!original_path.empty()) {
1120 // We don't know the encoding of inputpath
1121 docstring const inputpath = from_utf8(latex_path(original_path));
1122 os << "\\makeatletter\n"
1123 << "\\def\\input@path{{"
1124 << inputpath << "/}}\n"
1125 << "\\makeatother\n";
1131 // Write the preamble
1132 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1138 os << "\\begin{document}\n";
1140 } // output_preamble
1142 texrow().start(paragraphs().begin()->id(), 0);
1144 LYXERR(Debug::INFO) << "preamble finished, now the body." << endl;
1146 if (!lyxrc.language_auto_begin &&
1147 !params().language->babel().empty()) {
1149 os << from_utf8(subst(lyxrc.language_command_begin,
1151 params().language->babel()))
1156 Encoding const & encoding = params().encoding();
1157 if (encoding.package() == Encoding::CJK) {
1158 // Open a CJK environment, since in contrast to the encodings
1159 // handled by inputenc the document encoding is not set in
1160 // the preamble if it is handled by CJK.sty.
1161 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1166 // if we are doing a real file with body, even if this is the
1167 // child of some other buffer, let's cut the link here.
1168 // This happens for example if only a child document is printed.
1169 string save_parentname;
1170 if (output_preamble) {
1171 save_parentname = params().parentname;
1172 params().parentname.erase();
1175 loadChildDocuments(*this);
1178 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1180 // Restore the parenthood if needed
1181 if (output_preamble)
1182 params().parentname = save_parentname;
1184 // add this just in case after all the paragraphs
1188 if (encoding.package() == Encoding::CJK) {
1189 // Close the open CJK environment.
1190 // latexParagraphs will have opened one even if the last text
1192 os << "\\end{CJK}\n";
1196 if (!lyxrc.language_auto_end &&
1197 !params().language->babel().empty()) {
1198 os << from_utf8(subst(lyxrc.language_command_end,
1200 params().language->babel()))
1205 if (output_preamble) {
1206 os << "\\end{document}\n";
1209 LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl;
1211 LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made."
1214 runparams_in.encoding = runparams.encoding;
1216 // Just to be sure. (Asger)
1219 LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl;
1220 LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1
1225 bool Buffer::isLatex() const
1227 return params().getTextClass().outputType() == LATEX;
1231 bool Buffer::isLiterate() const
1233 return params().getTextClass().outputType() == LITERATE;
1237 bool Buffer::isDocBook() const
1239 return params().getTextClass().outputType() == DOCBOOK;
1243 void Buffer::makeDocBookFile(FileName const & fname,
1244 OutputParams const & runparams,
1245 bool const body_only)
1247 LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl;
1251 if (!openFileWrite(ofs, fname))
1254 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1258 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1262 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1263 OutputParams const & runparams,
1264 bool const only_body)
1266 LaTeXFeatures features(*this, params(), runparams);
1271 TextClass const & tclass = params().getTextClass();
1272 string const top_element = tclass.latexname();
1275 if (runparams.flavor == OutputParams::XML)
1276 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1279 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1282 if (! tclass.class_header().empty())
1283 os << from_ascii(tclass.class_header());
1284 else if (runparams.flavor == OutputParams::XML)
1285 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1286 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1288 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1290 docstring preamble = from_utf8(params().preamble);
1291 if (runparams.flavor != OutputParams::XML ) {
1292 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1293 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1294 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1295 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1298 string const name = runparams.nice ? changeExtension(fileName(), ".sgml")
1300 preamble += features.getIncludedFiles(name);
1301 preamble += features.getLyXSGMLEntities();
1303 if (!preamble.empty()) {
1304 os << "\n [ " << preamble << " ]";
1309 string top = top_element;
1311 if (runparams.flavor == OutputParams::XML)
1312 top += params().language->code();
1314 top += params().language->code().substr(0,2);
1317 if (!params().options.empty()) {
1319 top += params().options;
1322 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1323 << " file was created by LyX " << lyx_version
1324 << "\n See http://www.lyx.org/ for more information -->\n";
1326 params().getTextClass().counters().reset();
1328 loadChildDocuments(*this);
1330 sgml::openTag(os, top);
1332 docbookParagraphs(paragraphs(), *this, os, runparams);
1333 sgml::closeTag(os, top_element);
1337 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1338 // Other flags: -wall -v0 -x
1339 int Buffer::runChktex()
1343 // get LaTeX-Filename
1344 FileName const path(temppath());
1345 string const name = addName(path.absFilename(), getLatexName());
1346 string const org_path = filePath();
1348 support::Path p(path); // path to LaTeX file
1349 message(_("Running chktex..."));
1351 // Generate the LaTeX file if neccessary
1352 OutputParams runparams(¶ms().encoding());
1353 runparams.flavor = OutputParams::LATEX;
1354 runparams.nice = false;
1355 makeLaTeXFile(FileName(name), org_path, runparams);
1358 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1359 int const res = chktex.run(terr); // run chktex
1362 Alert::error(_("chktex failure"),
1363 _("Could not run chktex successfully."));
1364 } else if (res > 0) {
1365 ErrorList & errorList = pimpl_->errorLists["ChkTeX"];
1366 // Clear out old errors
1368 // Fill-in the error list with the TeX errors
1369 bufferErrors(*this, terr, errorList);
1380 void Buffer::validate(LaTeXFeatures & features) const
1382 TextClass const & tclass = params().getTextClass();
1384 if (params().outputChanges) {
1385 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1386 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1387 LaTeXFeatures::isAvailable("xcolor");
1389 if (features.runparams().flavor == OutputParams::LATEX) {
1391 features.require("ct-dvipost");
1392 features.require("dvipost");
1393 } else if (xcolorsoul) {
1394 features.require("ct-xcolor-soul");
1395 features.require("soul");
1396 features.require("xcolor");
1398 features.require("ct-none");
1400 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1402 features.require("ct-xcolor-soul");
1403 features.require("soul");
1404 features.require("xcolor");
1405 features.require("pdfcolmk"); // improves color handling in PDF output
1407 features.require("ct-none");
1412 // AMS Style is at document level
1413 if (params().use_amsmath == BufferParams::package_on
1414 || tclass.provides("amsmath"))
1415 features.require("amsmath");
1416 if (params().use_esint == BufferParams::package_on)
1417 features.require("esint");
1419 loadChildDocuments(*this);
1421 for_each(paragraphs().begin(), paragraphs().end(),
1422 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1424 // the bullet shapes are buffer level not paragraph level
1425 // so they are tested here
1426 for (int i = 0; i < 4; ++i) {
1427 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1428 int const font = params().user_defined_bullet(i).getFont();
1430 int const c = params()
1431 .user_defined_bullet(i)
1438 features.require("latexsym");
1440 } else if (font == 1) {
1441 features.require("amssymb");
1442 } else if ((font >= 2 && font <= 5)) {
1443 features.require("pifont");
1448 if (lyxerr.debugging(Debug::LATEX)) {
1449 features.showStruct();
1454 void Buffer::getLabelList(vector<docstring> & list) const
1456 /// if this is a child document and the parent is already loaded
1457 /// Use the parent's list instead [ale990407]
1458 Buffer const * tmp = getMasterBuffer();
1460 lyxerr << "getMasterBuffer() failed!" << endl;
1464 tmp->getLabelList(list);
1468 loadChildDocuments(*this);
1470 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1471 it.nextInset()->getLabelList(*this, list);
1475 void Buffer::updateBibfilesCache()
1477 // if this is a child document and the parent is already loaded
1478 // update the parent's cache instead
1479 Buffer * tmp = getMasterBuffer();
1482 tmp->updateBibfilesCache();
1486 bibfilesCache_.clear();
1487 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1488 if (it->lyxCode() == Inset::BIBTEX_CODE) {
1489 InsetBibtex const & inset =
1490 static_cast<InsetBibtex const &>(*it);
1491 vector<FileName> const bibfiles = inset.getFiles(*this);
1492 bibfilesCache_.insert(bibfilesCache_.end(),
1495 } else if (it->lyxCode() == Inset::INCLUDE_CODE) {
1496 InsetInclude & inset =
1497 static_cast<InsetInclude &>(*it);
1498 inset.updateBibfilesCache(*this);
1499 vector<FileName> const & bibfiles =
1500 inset.getBibfilesCache(*this);
1501 bibfilesCache_.insert(bibfilesCache_.end(),
1509 vector<FileName> const & Buffer::getBibfilesCache() const
1511 // if this is a child document and the parent is already loaded
1512 // use the parent's cache instead
1513 Buffer const * tmp = getMasterBuffer();
1516 return tmp->getBibfilesCache();
1518 // We update the cache when first used instead of at loading time.
1519 if (bibfilesCache_.empty())
1520 const_cast<Buffer *>(this)->updateBibfilesCache();
1522 return bibfilesCache_;
1526 bool Buffer::isDepClean(string const & name) const
1528 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1529 if (it == pimpl_->dep_clean.end())
1535 void Buffer::markDepClean(string const & name)
1537 pimpl_->dep_clean[name] = true;
1541 bool Buffer::dispatch(string const & command, bool * result)
1543 return dispatch(lyxaction.lookupFunc(command), result);
1547 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1549 bool dispatched = true;
1551 switch (func.action) {
1552 case LFUN_BUFFER_EXPORT: {
1553 bool const tmp = Exporter::Export(this, to_utf8(func.argument()), false);
1566 void Buffer::changeLanguage(Language const * from, Language const * to)
1571 for_each(par_iterator_begin(),
1573 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1577 bool Buffer::isMultiLingual() const
1579 ParConstIterator end = par_iterator_end();
1580 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1581 if (it->isMultiLingual(params()))
1588 ParIterator Buffer::getParFromID(int const id) const
1590 ParConstIterator it = par_iterator_begin();
1591 ParConstIterator const end = par_iterator_end();
1594 // John says this is called with id == -1 from undo
1595 lyxerr << "getParFromID(), id: " << id << endl;
1599 for (; it != end; ++it)
1607 bool Buffer::hasParWithID(int const id) const
1609 ParConstIterator const it = getParFromID(id);
1610 return it != par_iterator_end();
1614 ParIterator Buffer::par_iterator_begin()
1616 return lyx::par_iterator_begin(inset());
1620 ParIterator Buffer::par_iterator_end()
1622 return lyx::par_iterator_end(inset());
1626 ParConstIterator Buffer::par_iterator_begin() const
1628 return lyx::par_const_iterator_begin(inset());
1632 ParConstIterator Buffer::par_iterator_end() const
1634 return lyx::par_const_iterator_end(inset());
1638 Language const * Buffer::getLanguage() const
1640 return params().language;
1644 docstring const Buffer::B_(string const & l10n) const
1646 return params().B_(l10n);
1650 bool Buffer::isClean() const
1652 return pimpl_->lyx_clean;
1656 bool Buffer::isBakClean() const
1658 return pimpl_->bak_clean;
1662 bool Buffer::isExternallyModified(CheckMethod method) const
1664 BOOST_ASSERT(fs::exists(pimpl_->filename.toFilesystemEncoding()));
1665 // if method == timestamp, check timestamp before checksum
1666 return (method == checksum_method
1667 || pimpl_->timestamp_ != fs::last_write_time(pimpl_->filename.toFilesystemEncoding()))
1668 && pimpl_->checksum_ != sum(pimpl_->filename);
1672 void Buffer::saveCheckSum(string const & file) const
1674 if (fs::exists(file)) {
1675 pimpl_->timestamp_ = fs::last_write_time(file);
1676 pimpl_->checksum_ = sum(FileName(file));
1678 // in the case of save to a new file.
1679 pimpl_->timestamp_ = 0;
1680 pimpl_->checksum_ = 0;
1685 void Buffer::markClean() const
1687 if (!pimpl_->lyx_clean) {
1688 pimpl_->lyx_clean = true;
1691 // if the .lyx file has been saved, we don't need an
1693 pimpl_->bak_clean = true;
1697 void Buffer::markBakClean() const
1699 pimpl_->bak_clean = true;
1703 void Buffer::setUnnamed(bool flag)
1705 pimpl_->unnamed = flag;
1709 bool Buffer::isUnnamed() const
1711 return pimpl_->unnamed;
1715 // FIXME: this function should be moved to buffer_pimpl.C
1716 void Buffer::markDirty()
1718 if (pimpl_->lyx_clean) {
1719 pimpl_->lyx_clean = false;
1722 pimpl_->bak_clean = false;
1724 DepClean::iterator it = pimpl_->dep_clean.begin();
1725 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1727 for (; it != end; ++it)
1732 string const Buffer::fileName() const
1734 return pimpl_->filename.absFilename();
1738 string const & Buffer::filePath() const
1740 return params().filepath;
1744 bool Buffer::isReadonly() const
1746 return pimpl_->read_only;
1750 void Buffer::setParentName(string const & name)
1752 if (name == pimpl_->filename.absFilename())
1753 // Avoids recursive include.
1754 params().parentname.clear();
1756 params().parentname = name;
1760 Buffer const * Buffer::getMasterBuffer() const
1762 if (!params().parentname.empty()
1763 && theBufferList().exists(params().parentname)) {
1764 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1765 //We need to check if the parent is us...
1766 //FIXME RECURSIVE INCLUDE
1767 //This is not sufficient, since recursive includes could be downstream.
1768 if (buf && buf != this)
1769 return buf->getMasterBuffer();
1776 Buffer * Buffer::getMasterBuffer()
1778 if (!params().parentname.empty()
1779 && theBufferList().exists(params().parentname)) {
1780 Buffer * buf = theBufferList().getBuffer(params().parentname);
1781 //We need to check if the parent is us...
1782 //FIXME RECURSIVE INCLUDE
1783 //This is not sufficient, since recursive includes could be downstream.
1784 if (buf && buf != this)
1785 return buf->getMasterBuffer();
1792 MacroData const & Buffer::getMacro(docstring const & name) const
1794 return pimpl_->macros.get(name);
1798 bool Buffer::hasMacro(docstring const & name) const
1800 return pimpl_->macros.has(name);
1804 void Buffer::insertMacro(docstring const & name, MacroData const & data)
1806 MacroTable::globalMacros().insert(name, data);
1807 pimpl_->macros.insert(name, data);
1811 void Buffer::buildMacros()
1813 // Start with global table.
1814 pimpl_->macros = MacroTable::globalMacros();
1817 ParagraphList const & pars = text().paragraphs();
1818 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1819 //lyxerr << "searching main par " << i
1820 // << " for macro definitions" << std::endl;
1821 InsetList const & insets = pars[i].insetlist;
1822 InsetList::const_iterator it = insets.begin();
1823 InsetList::const_iterator end = insets.end();
1824 for ( ; it != end; ++it) {
1825 //lyxerr << "found inset code " << it->inset->lyxCode() << std::endl;
1826 if (it->inset->lyxCode() == Inset::MATHMACRO_CODE) {
1827 MathMacroTemplate const & mac
1828 = static_cast<MathMacroTemplate const &>(*it->inset);
1829 insertMacro(mac.name(), mac.asMacroData());
1836 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1839 Inset::Code code = static_cast<Inset::Code>(inset_code);
1841 //FIXME: This does not work for child documents yet.
1842 BOOST_ASSERT(code == Inset::CITE_CODE || code == Inset::REF_CODE);
1843 // Check if the label 'from' appears more than once
1844 vector<docstring> labels;
1846 if (code == Inset::CITE_CODE) {
1848 keys.fillWithBibKeys(this);
1849 BiblioInfo::const_iterator bit = keys.begin();
1850 BiblioInfo::const_iterator bend = keys.end();
1852 for (; bit != bend; ++bit)
1854 labels.push_back(bit->first);
1856 getLabelList(labels);
1858 if (std::count(labels.begin(), labels.end(), from) > 1)
1861 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1862 if (it->lyxCode() == code) {
1863 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1864 inset.replaceContents(to_utf8(from), to_utf8(to));
1870 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1871 pit_type par_end, bool full_source)
1873 OutputParams runparams(¶ms().encoding());
1874 runparams.nice = true;
1875 runparams.flavor = OutputParams::LATEX;
1876 runparams.linelen = lyxrc.plaintext_linelen;
1877 // No side effect of file copying and image conversion
1878 runparams.dryrun = true;
1882 os << "% " << _("Preview source code") << "\n\n";
1886 writeLaTeXSource(os, filePath(), runparams, true, true);
1888 writeDocBookSource(os, fileName(), runparams, false);
1891 runparams.par_begin = par_begin;
1892 runparams.par_end = par_end;
1893 if (par_begin + 1 == par_end)
1895 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1899 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1900 convert<docstring>(par_begin),
1901 convert<docstring>(par_end - 1))
1905 // output paragraphs
1907 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1910 docbookParagraphs(paragraphs(), *this, os, runparams);
1916 ErrorList const & Buffer::errorList(string const & type) const
1918 static ErrorList const emptyErrorList;
1919 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1920 if (I == pimpl_->errorLists.end())
1921 return emptyErrorList;
1927 ErrorList & Buffer::errorList(string const & type)
1929 return pimpl_->errorLists[type];
1933 void Buffer::structureChanged() const
1936 gui_->structureChanged();
1940 void Buffer::embeddingChanged() const
1943 gui_->embeddingChanged();
1947 void Buffer::errors(std::string const & err) const
1954 void Buffer::message(docstring const & msg) const
1961 void Buffer::busy(bool on) const
1968 void Buffer::readonly(bool on) const
1975 void Buffer::updateTitles() const
1978 gui_->updateTitles();
1982 void Buffer::resetAutosaveTimers() const
1985 gui_->resetAutosaveTimers();
1989 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
1998 class AutoSaveBuffer : public support::ForkedProcess {
2001 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
2002 : buffer_(buffer), fname_(fname) {}
2004 virtual boost::shared_ptr<ForkedProcess> clone() const
2006 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
2011 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
2012 from_utf8(fname_.absFilename())));
2013 return run(DontWait);
2017 virtual int generateChild();
2019 Buffer const & buffer_;
2024 #if !defined (HAVE_FORK)
2028 int AutoSaveBuffer::generateChild()
2030 // tmp_ret will be located (usually) in /tmp
2031 // will that be a problem?
2032 pid_t const pid = fork();
2033 // If you want to debug the autosave
2034 // you should set pid to -1, and comment out the fork.
2035 if (pid == 0 || pid == -1) {
2036 // pid = -1 signifies that lyx was unable
2037 // to fork. But we will do the save
2039 bool failed = false;
2041 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2042 if (!tmp_ret.empty()) {
2043 buffer_.writeFile(tmp_ret);
2044 // assume successful write of tmp_ret
2045 if (!rename(tmp_ret, fname_)) {
2047 // most likely couldn't move between
2048 // filesystems unless write of tmp_ret
2049 // failed so remove tmp file (if it
2058 // failed to write/rename tmp_ret so try writing direct
2059 if (!buffer_.writeFile(fname_)) {
2060 // It is dangerous to do this in the child,
2061 // but safe in the parent, so...
2062 if (pid == -1) // emit message signal.
2063 buffer_.message(_("Autosave failed!"));
2066 if (pid == 0) { // we are the child so...
2076 // Perfect target for a thread...
2077 void Buffer::autoSave() const
2079 if (isBakClean() || isReadonly()) {
2080 // We don't save now, but we'll try again later
2081 resetAutosaveTimers();
2085 // emit message signal.
2086 message(_("Autosaving current document..."));
2088 // create autosave filename
2089 string fname = filePath();
2091 fname += onlyFilename(fileName());
2094 AutoSaveBuffer autosave(*this, FileName(fname));
2098 resetAutosaveTimers();
2102 /** Write a buffer to a new file name and rename the buffer
2103 according to the new file name.
2105 This function is e.g. used by menu callbacks and
2106 LFUN_BUFFER_WRITE_AS.
2108 If 'newname' is empty (the default), the user is asked via a
2109 dialog for the buffer's new name and location.
2111 If 'newname' is non-empty and has an absolute path, that is used.
2112 Otherwise the base directory of the buffer is used as the base
2113 for any relative path in 'newname'.
2116 bool Buffer::writeAs(string const & newname)
2118 string fname = fileName();
2119 string const oldname = fname;
2121 if (newname.empty()) { /// No argument? Ask user through dialog
2124 FileDialog fileDlg(_("Choose a filename to save document as"),
2125 LFUN_BUFFER_WRITE_AS,
2126 make_pair(_("Documents|#o#O"),
2127 from_utf8(lyxrc.document_path)),
2128 make_pair(_("Templates|#T#t"),
2129 from_utf8(lyxrc.template_path)));
2131 if (!support::isLyXFilename(fname))
2134 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2136 FileDialog::Result result =
2137 fileDlg.save(from_utf8(onlyPath(fname)),
2139 from_utf8(onlyFilename(fname)));
2141 if (result.first == FileDialog::Later)
2144 fname = to_utf8(result.second);
2149 // Make sure the absolute filename ends with appropriate suffix
2150 fname = makeAbsPath(fname).absFilename();
2151 if (!support::isLyXFilename(fname))
2155 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2157 if (fs::exists(FileName(fname).toFilesystemEncoding())) {
2158 docstring const file = makeDisplayPath(fname, 30);
2159 docstring text = bformat(_("The document %1$s already "
2160 "exists.\n\nDo you want to "
2161 "overwrite that document?"),
2163 int const ret = Alert::prompt(_("Overwrite document?"),
2164 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2170 // Ok, change the name of the buffer
2173 bool unnamed = isUnnamed();
2175 saveCheckSum(fname);
2178 setFileName(oldname);
2179 setUnnamed(unnamed);
2180 saveCheckSum(oldname);
2184 removeAutosaveFile(oldname);
2189 bool Buffer::menuWrite()
2192 LyX::ref().session().lastFiles().add(FileName(fileName()));
2196 // FIXME: we don't tell the user *WHY* the save failed !!
2198 docstring const file = makeDisplayPath(fileName(), 30);
2200 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2201 "Do you want to rename the document and "
2202 "try again?"), file);
2203 int const ret = Alert::prompt(_("Rename and save?"),
2204 text, 0, 1, _("&Rename"), _("&Cancel"));