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;
125 using support::addName;
126 using support::bformat;
127 using support::changeExtension;
128 using support::cmd_ret;
129 using support::createBufferTmpDir;
130 using support::destroyDir;
131 using support::FileName;
132 using support::getFormatFromContents;
133 using support::libFileSearch;
134 using support::latex_path;
135 using support::ltrim;
136 using support::makeAbsPath;
137 using support::makeDisplayPath;
138 using support::makeLatexName;
139 using support::onlyFilename;
140 using support::onlyPath;
141 using support::quoteName;
142 using support::removeAutosaveFile;
143 using support::rename;
144 using support::runCommand;
145 using support::split;
146 using support::subst;
147 using support::tempName;
150 using support::suffixIs;
152 namespace Alert = frontend::Alert;
153 namespace os = support::os;
154 namespace fs = boost::filesystem;
158 int const LYX_FORMAT = 290; //Uwe Stöhr, wrap table
163 typedef std::map<string, bool> DepClean;
168 Impl(Buffer & parent, FileName const & file, bool readonly);
170 limited_stack<Undo> undostack;
171 limited_stack<Undo> redostack;
177 /// need to regenerate .tex?
181 mutable bool lyx_clean;
183 /// is autosave needed?
184 mutable bool bak_clean;
186 /// is this a unnamed file (New...)?
192 /// name of the file the buffer is associated with.
195 /** Set to true only when the file is fully loaded.
196 * Used to prevent the premature generation of previews
197 * and by the citation inset.
199 bool file_fully_loaded;
201 /// our Text that should be wrapped in an InsetText
208 TocBackend toc_backend;
210 /// Container for all sort of Buffer dependant errors.
211 map<string, ErrorList> errorLists;
213 /// all embedded files of this buffer
214 EmbeddedFiles embedded_files;
216 /// timestamp and checksum used to test if the file has been externally
217 /// modified. (Used to properly enable 'File->Revert to saved', bug 4114).
219 unsigned long checksum_;
222 frontend::WorkAreaManager * wa_;
226 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
227 : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
228 filename(file), file_fully_loaded(false), inset(params),
229 toc_backend(&parent), embedded_files(&parent), timestamp_(0),
232 inset.setAutoBreakRows(true);
233 lyxvc.buffer(&parent);
234 temppath = createBufferTmpDir();
235 params.filepath = onlyPath(file.absFilename());
236 // FIXME: And now do something if temppath == string(), because we
237 // assume from now on that temppath points to a valid temp dir.
238 // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html
241 wa_ = new frontend::WorkAreaManager;
245 Buffer::Buffer(string const & file, bool readonly)
246 : pimpl_(new Impl(*this, FileName(file), readonly)), gui_(0)
248 LYXERR(Debug::INFO) << "Buffer::Buffer()" << endl;
254 LYXERR(Debug::INFO) << "Buffer::~Buffer()" << endl;
255 // here the buffer should take care that it is
256 // saved properly, before it goes into the void.
258 Buffer * master = getMasterBuffer();
259 if (master != this && use_gui)
260 // We are closing buf which was a child document so we
261 // must update the labels and section numbering of its master
263 updateLabels(*master);
265 if (!temppath().empty() && !destroyDir(FileName(temppath()))) {
266 Alert::warning(_("Could not remove temporary directory"),
267 bformat(_("Could not remove the temporary directory %1$s"),
268 from_utf8(temppath())));
271 // Remove any previewed LaTeX snippets associated with this buffer.
272 graphics::Previews::get().removeLoader(*this);
275 pimpl_->wa_->closeAll();
282 void Buffer::changed() const
285 pimpl_->wa_->redrawAll();
289 frontend::WorkAreaManager & Buffer::workAreaManager() const
291 BOOST_ASSERT(pimpl_->wa_);
296 Text & Buffer::text() const
298 return const_cast<Text &>(pimpl_->inset.text_);
302 Inset & Buffer::inset() const
304 return const_cast<InsetText &>(pimpl_->inset);
308 limited_stack<Undo> & Buffer::undostack()
310 return pimpl_->undostack;
314 limited_stack<Undo> const & Buffer::undostack() const
316 return pimpl_->undostack;
320 limited_stack<Undo> & Buffer::redostack()
322 return pimpl_->redostack;
326 limited_stack<Undo> const & Buffer::redostack() const
328 return pimpl_->redostack;
332 BufferParams & Buffer::params()
334 return pimpl_->params;
338 BufferParams const & Buffer::params() const
340 return pimpl_->params;
344 ParagraphList & Buffer::paragraphs()
346 return text().paragraphs();
350 ParagraphList const & Buffer::paragraphs() const
352 return text().paragraphs();
356 LyXVC & Buffer::lyxvc()
358 return pimpl_->lyxvc;
362 LyXVC const & Buffer::lyxvc() const
364 return pimpl_->lyxvc;
368 string const & Buffer::temppath() const
370 return pimpl_->temppath;
374 TexRow & Buffer::texrow()
376 return pimpl_->texrow;
380 TexRow const & Buffer::texrow() const
382 return pimpl_->texrow;
386 TocBackend & Buffer::tocBackend()
388 return pimpl_->toc_backend;
392 TocBackend const & Buffer::tocBackend() const
394 return pimpl_->toc_backend;
398 EmbeddedFiles & Buffer::embeddedFiles()
400 return pimpl_->embedded_files;
404 EmbeddedFiles const & Buffer::embeddedFiles() const
406 return pimpl_->embedded_files;
410 string const Buffer::getLatexName(bool const no_path) const
412 string const name = changeExtension(makeLatexName(fileName()), ".tex");
413 return no_path ? onlyFilename(name) : name;
417 pair<Buffer::LogType, string> const Buffer::getLogName() const
419 string const filename = getLatexName(false);
421 if (filename.empty())
422 return make_pair(Buffer::latexlog, string());
424 string const path = temppath();
426 FileName const fname(addName(temppath(),
427 onlyFilename(changeExtension(filename,
429 FileName const bname(
430 addName(path, onlyFilename(
431 changeExtension(filename,
432 formats.extension("literate") + ".out"))));
434 // If no Latex log or Build log is newer, show Build log
436 if (fs::exists(bname.toFilesystemEncoding()) &&
437 (!fs::exists(fname.toFilesystemEncoding()) ||
438 fs::last_write_time(fname.toFilesystemEncoding()) < fs::last_write_time(bname.toFilesystemEncoding()))) {
439 LYXERR(Debug::FILES) << "Log name calculated as: " << bname << endl;
440 return make_pair(Buffer::buildlog, bname.absFilename());
442 LYXERR(Debug::FILES) << "Log name calculated as: " << fname << endl;
443 return make_pair(Buffer::latexlog, fname.absFilename());
447 void Buffer::setReadonly(bool const flag)
449 if (pimpl_->read_only != flag) {
450 pimpl_->read_only = flag;
456 void Buffer::setFileName(string const & newfile)
458 pimpl_->filename = makeAbsPath(newfile);
459 params().filepath = onlyPath(pimpl_->filename.absFilename());
460 setReadonly(fs::is_readonly(pimpl_->filename.toFilesystemEncoding()));
465 // We'll remove this later. (Lgb)
468 void unknownClass(string const & unknown)
470 Alert::warning(_("Unknown document class"),
471 bformat(_("Using the default document class, because the "
472 "class %1$s is unknown."), from_utf8(unknown)));
478 int Buffer::readHeader(Lexer & lex)
480 int unknown_tokens = 0;
482 int begin_header_line = -1;
484 // Initialize parameters that may be/go lacking in header:
485 params().branchlist().clear();
486 params().preamble.erase();
487 params().options.erase();
488 params().float_placement.erase();
489 params().paperwidth.erase();
490 params().paperheight.erase();
491 params().leftmargin.erase();
492 params().rightmargin.erase();
493 params().topmargin.erase();
494 params().bottommargin.erase();
495 params().headheight.erase();
496 params().headsep.erase();
497 params().footskip.erase();
498 params().listings_params.clear();
499 params().clearLayoutModules();
500 params().pdfoptions().clear();
502 for (int i = 0; i < 4; ++i) {
503 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
504 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
507 ErrorList & errorList = pimpl_->errorLists["Parse"];
511 string const token = lex.getString();
516 if (token == "\\end_header")
520 if (token == "\\begin_header") {
521 begin_header_line = line;
525 LYXERR(Debug::PARSER) << "Handling document header token: `"
526 << token << '\'' << endl;
528 string unknown = params().readToken(lex, token);
529 if (!unknown.empty()) {
530 if (unknown[0] != '\\' && token == "\\textclass") {
531 unknownClass(unknown);
534 docstring const s = bformat(_("Unknown token: "
538 errorList.push_back(ErrorItem(_("Document header error"),
543 if (begin_header_line) {
544 docstring const s = _("\\begin_header is missing");
545 errorList.push_back(ErrorItem(_("Document header error"),
549 return unknown_tokens;
554 // changed to be public and have one parameter
555 // Returns false if "\end_document" is not read (Asger)
556 bool Buffer::readDocument(Lexer & lex)
558 ErrorList & errorList = pimpl_->errorLists["Parse"];
562 string const token = lex.getString();
563 if (token != "\\begin_document") {
564 docstring const s = _("\\begin_document is missing");
565 errorList.push_back(ErrorItem(_("Document header error"),
569 // we are reading in a brand new document
570 BOOST_ASSERT(paragraphs().empty());
573 TextClass const & baseClass = textclasslist[params().getBaseClass()];
574 if (!baseClass.load(filePath())) {
575 string theclass = baseClass.name();
576 Alert::error(_("Can't load document class"), bformat(
577 _("Using the default document class, because the "
578 "class %1$s could not be loaded."), from_utf8(theclass)));
579 params().setBaseClass(defaultTextclass());
582 if (params().outputChanges) {
583 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
584 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
585 LaTeXFeatures::isAvailable("xcolor");
587 if (!dvipost && !xcolorsoul) {
588 Alert::warning(_("Changes not shown in LaTeX output"),
589 _("Changes will not be highlighted in LaTeX output, "
590 "because neither dvipost nor xcolor/soul are installed.\n"
591 "Please install these packages or redefine "
592 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
593 } else if (!xcolorsoul) {
594 Alert::warning(_("Changes not shown in LaTeX output"),
595 _("Changes will not be highlighted in LaTeX output "
596 "when using pdflatex, because xcolor and soul are not installed.\n"
597 "Please install both packages or redefine "
598 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
601 // read manifest after header
602 embeddedFiles().readManifest(lex, errorList);
605 bool const res = text().read(*this, lex, errorList);
606 for_each(text().paragraphs().begin(),
607 text().paragraphs().end(),
608 bind(&Paragraph::setInsetOwner, _1, &inset()));
614 // needed to insert the selection
615 void Buffer::insertStringAsLines(ParagraphList & pars,
616 pit_type & pit, pos_type & pos,
617 Font const & fn, docstring const & str, bool autobreakrows)
621 // insert the string, don't insert doublespace
622 bool space_inserted = true;
623 for (docstring::const_iterator cit = str.begin();
624 cit != str.end(); ++cit) {
625 Paragraph & par = pars[pit];
627 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
628 breakParagraph(params(), pars, pit, pos,
629 par.layout()->isEnvironment());
632 space_inserted = true;
636 // do not insert consecutive spaces if !free_spacing
637 } else if ((*cit == ' ' || *cit == '\t') &&
638 space_inserted && !par.isFreeSpacing()) {
640 } else if (*cit == '\t') {
641 if (!par.isFreeSpacing()) {
642 // tabs are like spaces here
643 par.insertChar(pos, ' ', font, params().trackChanges);
645 space_inserted = true;
647 const pos_type n = 8 - pos % 8;
648 for (pos_type i = 0; i < n; ++i) {
649 par.insertChar(pos, ' ', font, params().trackChanges);
652 space_inserted = true;
654 } else if (!isPrintable(*cit)) {
655 // Ignore unprintables
658 // just insert the character
659 par.insertChar(pos, *cit, font, params().trackChanges);
661 space_inserted = (*cit == ' ');
668 bool Buffer::readString(std::string const & s)
670 params().compressed = false;
672 // remove dummy empty par
673 paragraphs().clear();
675 std::istringstream is(s);
677 FileName const name(tempName());
678 switch (readFile(lex, name, true)) {
682 // We need to call lyx2lyx, so write the input to a file
683 std::ofstream os(name.toFilesystemEncoding().c_str());
686 return readFile(name);
696 bool Buffer::readFile(FileName const & filename)
698 FileName fname(filename);
699 // Check if the file is compressed.
700 string format = getFormatFromContents(filename);
701 if (format == "zip") {
702 // decompress to a temp directory
703 LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl;
704 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
706 FileName lyxfile(addName(temppath(), "content.lyx"));
707 // if both manifest.txt and file.lyx exist, this is am embedded file
708 if (fs::exists(lyxfile.toFilesystemEncoding())) {
709 params().embedded = true;
713 // The embedded lyx file can also be compressed, for backward compatibility
714 format = getFormatFromContents(fname);
715 if (format == "gzip" || format == "zip" || format == "compress") {
716 params().compressed = true;
719 // remove dummy empty par
720 paragraphs().clear();
723 if (readFile(lex, fname) != success)
730 bool Buffer::fully_loaded() const
732 return pimpl_->file_fully_loaded;
736 void Buffer::fully_loaded(bool const value)
738 pimpl_->file_fully_loaded = value;
742 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
745 BOOST_ASSERT(!filename.empty());
748 Alert::error(_("Document could not be read"),
749 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
754 string const token(lex.getString());
757 Alert::error(_("Document could not be read"),
758 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
762 // the first token _must_ be...
763 if (token != "\\lyxformat") {
764 lyxerr << "Token: " << token << endl;
766 Alert::error(_("Document format failure"),
767 bformat(_("%1$s is not a LyX document."),
768 from_utf8(filename.absFilename())));
773 string tmp_format = lex.getString();
774 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
775 // if present remove ".," from string.
776 string::size_type dot = tmp_format.find_first_of(".,");
777 //lyxerr << " dot found at " << dot << endl;
778 if (dot != string::npos)
779 tmp_format.erase(dot, 1);
780 int const file_format = convert<int>(tmp_format);
781 //lyxerr << "format: " << file_format << endl;
783 // save timestamp and checksum of the original disk file, making sure
784 // to not overwrite them with those of the file created in the tempdir
785 // when it has to be converted to the current format.
786 if (!pimpl_->checksum_) {
787 // Save the timestamp and checksum of disk file. If filename is an
788 // emergency file, save the timestamp and sum of the original lyx file
789 // because isExternallyModified will check for this file. (BUG4193)
790 string diskfile = filename.toFilesystemEncoding();
791 if (suffixIs(diskfile, ".emergency"))
792 diskfile = diskfile.substr(0, diskfile.size() - 10);
793 saveCheckSum(diskfile);
796 if (file_format != LYX_FORMAT) {
799 // lyx2lyx would fail
802 FileName const tmpfile(tempName());
803 if (tmpfile.empty()) {
804 Alert::error(_("Conversion failed"),
805 bformat(_("%1$s is from a different"
806 " version of LyX, but a temporary"
807 " file for converting it could"
809 from_utf8(filename.absFilename())));
812 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
813 if (lyx2lyx.empty()) {
814 Alert::error(_("Conversion script not found"),
815 bformat(_("%1$s is from a different"
816 " version of LyX, but the"
817 " conversion script lyx2lyx"
818 " could not be found."),
819 from_utf8(filename.absFilename())));
822 ostringstream command;
823 command << os::python()
824 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
825 << " -t " << convert<string>(LYX_FORMAT)
826 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
827 << ' ' << quoteName(filename.toFilesystemEncoding());
828 string const command_str = command.str();
830 LYXERR(Debug::INFO) << "Running '"
831 << command_str << '\''
834 cmd_ret const ret = runCommand(command_str);
835 if (ret.first != 0) {
836 Alert::error(_("Conversion script failed"),
837 bformat(_("%1$s is from a different version"
838 " of LyX, but the lyx2lyx script"
839 " failed to convert it."),
840 from_utf8(filename.absFilename())));
843 bool const ret = readFile(tmpfile);
844 // Do stuff with tmpfile name and buffer name here.
845 return ret ? success : failure;
850 if (readDocument(lex)) {
851 Alert::error(_("Document format failure"),
852 bformat(_("%1$s ended unexpectedly, which means"
853 " that it is probably corrupted."),
854 from_utf8(filename.absFilename())));
857 //lyxerr << "removing " << MacroTable::localMacros().size()
858 // << " temporary macro entries" << endl;
859 //MacroTable::localMacros().clear();
861 pimpl_->file_fully_loaded = true;
866 // Should probably be moved to somewhere else: BufferView? LyXView?
867 bool Buffer::save() const
869 // We don't need autosaves in the immediate future. (Asger)
870 resetAutosaveTimers();
872 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
875 bool madeBackup = false;
877 // make a backup if the file already exists
878 if (lyxrc.make_backup && fs::exists(encodedFilename)) {
879 backupName = FileName(fileName() + '~');
880 if (!lyxrc.backupdir_path.empty())
881 backupName = FileName(addName(lyxrc.backupdir_path,
882 subst(os::internal_path(backupName.absFilename()), '/', '!')));
885 fs::copy_file(encodedFilename, backupName.toFilesystemEncoding(), false);
887 } catch (fs::filesystem_error const & fe) {
888 Alert::error(_("Backup failure"),
889 bformat(_("Cannot create backup file %1$s.\n"
890 "Please check whether the directory exists and is writeable."),
891 from_utf8(backupName.absFilename())));
892 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
896 // ask if the disk file has been externally modified (use checksum method)
897 if (fs::exists(encodedFilename) && isExternallyModified(checksum_method)) {
898 docstring const file = makeDisplayPath(fileName(), 20);
899 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
900 "you want to overwrite this file?"), file);
901 int const ret = Alert::prompt(_("Overwrite modified file?"),
902 text, 1, 1, _("&Overwrite"), _("&Cancel"));
907 if (writeFile(pimpl_->filename)) {
909 removeAutosaveFile(fileName());
910 saveCheckSum(pimpl_->filename.toFilesystemEncoding());
913 // Saving failed, so backup is not backup
915 rename(backupName, pimpl_->filename);
921 bool Buffer::writeFile(FileName const & fname) const
923 if (pimpl_->read_only && fname == pimpl_->filename)
929 if (params().embedded)
930 // first write the .lyx file to the temporary directory
931 content = FileName(addName(temppath(), "content.lyx"));
935 if (params().compressed) {
936 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
942 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
949 if (retval && params().embedded) {
950 // write file.lyx and all the embedded files to the zip file fname
951 // if embedding is enabled
952 return pimpl_->embedded_files.writeFile(fname);
958 bool Buffer::write(ostream & ofs) const
961 // Use the standard "C" locale for file output.
962 ofs.imbue(std::locale::classic());
965 // The top of the file should not be written by params().
967 // write out a comment in the top of the file
968 ofs << "#LyX " << lyx_version
969 << " created this file. For more info see http://www.lyx.org/\n"
970 << "\\lyxformat " << LYX_FORMAT << "\n"
971 << "\\begin_document\n";
974 /// For each author, set 'used' to true if there is a change
975 /// by this author in the document; otherwise set it to 'false'.
976 AuthorList::Authors::const_iterator a_it = params().authors().begin();
977 AuthorList::Authors::const_iterator a_end = params().authors().end();
978 for (; a_it != a_end; ++a_it)
979 a_it->second.used(false);
981 ParIterator const end = par_iterator_end();
982 ParIterator it = par_iterator_begin();
983 for ( ; it != end; ++it)
984 it->checkAuthors(params().authors());
986 // now write out the buffer parameters.
987 ofs << "\\begin_header\n";
988 params().writeFile(ofs);
989 ofs << "\\end_header\n";
991 // write the manifest after header
992 ofs << "\n\\begin_manifest\n";
993 pimpl_->embedded_files.update();
994 embeddedFiles().writeManifest(ofs);
995 ofs << "\\end_manifest\n";
998 ofs << "\n\\begin_body\n";
999 text().write(*this, ofs);
1000 ofs << "\n\\end_body\n";
1002 // Write marker that shows file is complete
1003 ofs << "\\end_document" << endl;
1005 // Shouldn't really be needed....
1008 // how to check if close went ok?
1009 // Following is an attempt... (BE 20001011)
1011 // good() returns false if any error occured, including some
1012 // formatting error.
1013 // bad() returns true if something bad happened in the buffer,
1014 // which should include file system full errors.
1019 lyxerr << "File was not closed properly." << endl;
1026 bool Buffer::makeLaTeXFile(FileName const & fname,
1027 string const & original_path,
1028 OutputParams const & runparams,
1029 bool output_preamble, bool output_body)
1031 string const encoding = runparams.encoding->iconvName();
1032 LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: "
1033 << encoding << "..." << endl;
1035 odocfstream ofs(encoding);
1036 if (!openFileWrite(ofs, fname))
1039 //TexStream ts(ofs.rdbuf(), &texrow());
1041 bool failed_export = false;
1044 writeLaTeXSource(ofs, original_path,
1045 runparams, output_preamble, output_body);
1047 catch (iconv_codecvt_facet_exception & e) {
1048 lyxerr << "Caught iconv exception: " << e.what() << endl;
1049 failed_export = true;
1051 catch (std::exception const & e) {
1052 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1053 failed_export = true;
1056 lyxerr << "Caught some really weird exception..." << endl;
1057 LyX::cref().emergencyCleanup();
1063 failed_export = true;
1064 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1067 if (failed_export) {
1068 Alert::error(_("Encoding error"),
1069 _("Some characters of your document are probably not "
1070 "representable in the chosen encoding.\n"
1071 "Changing the document encoding to utf8 could help."));
1078 void Buffer::writeLaTeXSource(odocstream & os,
1079 string const & original_path,
1080 OutputParams const & runparams_in,
1081 bool const output_preamble, bool const output_body)
1083 OutputParams runparams = runparams_in;
1085 // validate the buffer.
1086 LYXERR(Debug::LATEX) << " Validating buffer..." << endl;
1087 LaTeXFeatures features(*this, params(), runparams);
1089 LYXERR(Debug::LATEX) << " Buffer validation done." << endl;
1091 // The starting paragraph of the coming rows is the
1092 // first paragraph of the document. (Asger)
1093 if (output_preamble && runparams.nice) {
1094 os << "%% LyX " << lyx_version << " created this file. "
1095 "For more info, see http://www.lyx.org/.\n"
1096 "%% Do not edit unless you really know what "
1101 LYXERR(Debug::INFO) << "lyx document header finished" << endl;
1102 // There are a few differences between nice LaTeX and usual files:
1103 // usual is \batchmode and has a
1104 // special input@path to allow the including of figures
1105 // with either \input or \includegraphics (what figinsets do).
1106 // input@path is set when the actual parameter
1107 // original_path is set. This is done for usual tex-file, but not
1108 // for nice-latex-file. (Matthias 250696)
1109 // Note that input@path is only needed for something the user does
1110 // in the preamble, included .tex files or ERT, files included by
1111 // LyX work without it.
1112 if (output_preamble) {
1113 if (!runparams.nice) {
1114 // code for usual, NOT nice-latex-file
1115 os << "\\batchmode\n"; // changed
1116 // from \nonstopmode
1119 if (!original_path.empty()) {
1121 // We don't know the encoding of inputpath
1122 docstring const inputpath = from_utf8(latex_path(original_path));
1123 os << "\\makeatletter\n"
1124 << "\\def\\input@path{{"
1125 << inputpath << "/}}\n"
1126 << "\\makeatother\n";
1132 // Write the preamble
1133 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1139 os << "\\begin{document}\n";
1141 } // output_preamble
1143 texrow().start(paragraphs().begin()->id(), 0);
1145 LYXERR(Debug::INFO) << "preamble finished, now the body." << endl;
1147 if (!lyxrc.language_auto_begin &&
1148 !params().language->babel().empty()) {
1150 os << from_utf8(subst(lyxrc.language_command_begin,
1152 params().language->babel()))
1157 Encoding const & encoding = params().encoding();
1158 if (encoding.package() == Encoding::CJK) {
1159 // Open a CJK environment, since in contrast to the encodings
1160 // handled by inputenc the document encoding is not set in
1161 // the preamble if it is handled by CJK.sty.
1162 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1167 // if we are doing a real file with body, even if this is the
1168 // child of some other buffer, let's cut the link here.
1169 // This happens for example if only a child document is printed.
1170 string save_parentname;
1171 if (output_preamble) {
1172 save_parentname = params().parentname;
1173 params().parentname.erase();
1176 loadChildDocuments(*this);
1179 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1181 // Restore the parenthood if needed
1182 if (output_preamble)
1183 params().parentname = save_parentname;
1185 // add this just in case after all the paragraphs
1189 if (encoding.package() == Encoding::CJK) {
1190 // Close the open CJK environment.
1191 // latexParagraphs will have opened one even if the last text
1193 os << "\\end{CJK}\n";
1197 if (!lyxrc.language_auto_end &&
1198 !params().language->babel().empty()) {
1199 os << from_utf8(subst(lyxrc.language_command_end,
1201 params().language->babel()))
1206 if (output_preamble) {
1207 os << "\\end{document}\n";
1210 LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl;
1212 LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made."
1215 runparams_in.encoding = runparams.encoding;
1217 // Just to be sure. (Asger)
1220 LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl;
1221 LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1
1226 bool Buffer::isLatex() const
1228 return params().getTextClass().outputType() == LATEX;
1232 bool Buffer::isLiterate() const
1234 return params().getTextClass().outputType() == LITERATE;
1238 bool Buffer::isDocBook() const
1240 return params().getTextClass().outputType() == DOCBOOK;
1244 void Buffer::makeDocBookFile(FileName const & fname,
1245 OutputParams const & runparams,
1246 bool const body_only)
1248 LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl;
1252 if (!openFileWrite(ofs, fname))
1255 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1259 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1263 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1264 OutputParams const & runparams,
1265 bool const only_body)
1267 LaTeXFeatures features(*this, params(), runparams);
1272 TextClass const & tclass = params().getTextClass();
1273 string const top_element = tclass.latexname();
1276 if (runparams.flavor == OutputParams::XML)
1277 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1280 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1283 if (! tclass.class_header().empty())
1284 os << from_ascii(tclass.class_header());
1285 else if (runparams.flavor == OutputParams::XML)
1286 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1287 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1289 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1291 docstring preamble = from_utf8(params().preamble);
1292 if (runparams.flavor != OutputParams::XML ) {
1293 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1294 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1295 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1296 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1299 string const name = runparams.nice ? changeExtension(fileName(), ".sgml")
1301 preamble += features.getIncludedFiles(name);
1302 preamble += features.getLyXSGMLEntities();
1304 if (!preamble.empty()) {
1305 os << "\n [ " << preamble << " ]";
1310 string top = top_element;
1312 if (runparams.flavor == OutputParams::XML)
1313 top += params().language->code();
1315 top += params().language->code().substr(0,2);
1318 if (!params().options.empty()) {
1320 top += params().options;
1323 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1324 << " file was created by LyX " << lyx_version
1325 << "\n See http://www.lyx.org/ for more information -->\n";
1327 params().getTextClass().counters().reset();
1329 loadChildDocuments(*this);
1331 sgml::openTag(os, top);
1333 docbookParagraphs(paragraphs(), *this, os, runparams);
1334 sgml::closeTag(os, top_element);
1338 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1339 // Other flags: -wall -v0 -x
1340 int Buffer::runChktex()
1344 // get LaTeX-Filename
1345 FileName const path(temppath());
1346 string const name = addName(path.absFilename(), getLatexName());
1347 string const org_path = filePath();
1349 support::Path p(path); // path to LaTeX file
1350 message(_("Running chktex..."));
1352 // Generate the LaTeX file if neccessary
1353 OutputParams runparams(¶ms().encoding());
1354 runparams.flavor = OutputParams::LATEX;
1355 runparams.nice = false;
1356 makeLaTeXFile(FileName(name), org_path, runparams);
1359 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1360 int const res = chktex.run(terr); // run chktex
1363 Alert::error(_("chktex failure"),
1364 _("Could not run chktex successfully."));
1365 } else if (res > 0) {
1366 ErrorList & errorList = pimpl_->errorLists["ChkTeX"];
1367 // Clear out old errors
1369 // Fill-in the error list with the TeX errors
1370 bufferErrors(*this, terr, errorList);
1381 void Buffer::validate(LaTeXFeatures & features) const
1383 TextClass const & tclass = params().getTextClass();
1385 if (params().outputChanges) {
1386 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1387 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1388 LaTeXFeatures::isAvailable("xcolor");
1390 if (features.runparams().flavor == OutputParams::LATEX) {
1392 features.require("ct-dvipost");
1393 features.require("dvipost");
1394 } else if (xcolorsoul) {
1395 features.require("ct-xcolor-soul");
1396 features.require("soul");
1397 features.require("xcolor");
1399 features.require("ct-none");
1401 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1403 features.require("ct-xcolor-soul");
1404 features.require("soul");
1405 features.require("xcolor");
1406 features.require("pdfcolmk"); // improves color handling in PDF output
1408 features.require("ct-none");
1413 // AMS Style is at document level
1414 if (params().use_amsmath == BufferParams::package_on
1415 || tclass.provides("amsmath"))
1416 features.require("amsmath");
1417 if (params().use_esint == BufferParams::package_on)
1418 features.require("esint");
1420 loadChildDocuments(*this);
1422 for_each(paragraphs().begin(), paragraphs().end(),
1423 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1425 // the bullet shapes are buffer level not paragraph level
1426 // so they are tested here
1427 for (int i = 0; i < 4; ++i) {
1428 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1429 int const font = params().user_defined_bullet(i).getFont();
1431 int const c = params()
1432 .user_defined_bullet(i)
1439 features.require("latexsym");
1441 } else if (font == 1) {
1442 features.require("amssymb");
1443 } else if ((font >= 2 && font <= 5)) {
1444 features.require("pifont");
1449 if (lyxerr.debugging(Debug::LATEX)) {
1450 features.showStruct();
1455 void Buffer::getLabelList(vector<docstring> & list) const
1457 /// if this is a child document and the parent is already loaded
1458 /// Use the parent's list instead [ale990407]
1459 Buffer const * tmp = getMasterBuffer();
1461 lyxerr << "getMasterBuffer() failed!" << endl;
1465 tmp->getLabelList(list);
1469 loadChildDocuments(*this);
1471 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1472 it.nextInset()->getLabelList(*this, list);
1476 void Buffer::updateBibfilesCache()
1478 // if this is a child document and the parent is already loaded
1479 // update the parent's cache instead
1480 Buffer * tmp = getMasterBuffer();
1483 tmp->updateBibfilesCache();
1487 bibfilesCache_.clear();
1488 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1489 if (it->lyxCode() == Inset::BIBTEX_CODE) {
1490 InsetBibtex const & inset =
1491 static_cast<InsetBibtex const &>(*it);
1492 vector<FileName> const bibfiles = inset.getFiles(*this);
1493 bibfilesCache_.insert(bibfilesCache_.end(),
1496 } else if (it->lyxCode() == Inset::INCLUDE_CODE) {
1497 InsetInclude & inset =
1498 static_cast<InsetInclude &>(*it);
1499 inset.updateBibfilesCache(*this);
1500 vector<FileName> const & bibfiles =
1501 inset.getBibfilesCache(*this);
1502 bibfilesCache_.insert(bibfilesCache_.end(),
1510 vector<FileName> const & Buffer::getBibfilesCache() const
1512 // if this is a child document and the parent is already loaded
1513 // use the parent's cache instead
1514 Buffer const * tmp = getMasterBuffer();
1517 return tmp->getBibfilesCache();
1519 // We update the cache when first used instead of at loading time.
1520 if (bibfilesCache_.empty())
1521 const_cast<Buffer *>(this)->updateBibfilesCache();
1523 return bibfilesCache_;
1527 bool Buffer::isDepClean(string const & name) const
1529 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1530 if (it == pimpl_->dep_clean.end())
1536 void Buffer::markDepClean(string const & name)
1538 pimpl_->dep_clean[name] = true;
1542 bool Buffer::dispatch(string const & command, bool * result)
1544 return dispatch(lyxaction.lookupFunc(command), result);
1548 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1550 bool dispatched = true;
1552 switch (func.action) {
1553 case LFUN_BUFFER_EXPORT: {
1554 bool const tmp = Exporter::Export(this, to_utf8(func.argument()), false);
1567 void Buffer::changeLanguage(Language const * from, Language const * to)
1572 for_each(par_iterator_begin(),
1574 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1578 bool Buffer::isMultiLingual() const
1580 ParConstIterator end = par_iterator_end();
1581 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1582 if (it->isMultiLingual(params()))
1589 ParIterator Buffer::getParFromID(int const id) const
1591 ParConstIterator it = par_iterator_begin();
1592 ParConstIterator const end = par_iterator_end();
1595 // John says this is called with id == -1 from undo
1596 lyxerr << "getParFromID(), id: " << id << endl;
1600 for (; it != end; ++it)
1608 bool Buffer::hasParWithID(int const id) const
1610 ParConstIterator const it = getParFromID(id);
1611 return it != par_iterator_end();
1615 ParIterator Buffer::par_iterator_begin()
1617 return lyx::par_iterator_begin(inset());
1621 ParIterator Buffer::par_iterator_end()
1623 return lyx::par_iterator_end(inset());
1627 ParConstIterator Buffer::par_iterator_begin() const
1629 return lyx::par_const_iterator_begin(inset());
1633 ParConstIterator Buffer::par_iterator_end() const
1635 return lyx::par_const_iterator_end(inset());
1639 Language const * Buffer::getLanguage() const
1641 return params().language;
1645 docstring const Buffer::B_(string const & l10n) const
1647 return params().B_(l10n);
1651 bool Buffer::isClean() const
1653 return pimpl_->lyx_clean;
1657 bool Buffer::isBakClean() const
1659 return pimpl_->bak_clean;
1663 bool Buffer::isExternallyModified(CheckMethod method) const
1665 BOOST_ASSERT(fs::exists(pimpl_->filename.toFilesystemEncoding()));
1666 // if method == timestamp, check timestamp before checksum
1667 return (method == checksum_method
1668 || pimpl_->timestamp_ != fs::last_write_time(pimpl_->filename.toFilesystemEncoding()))
1669 && pimpl_->checksum_ != sum(pimpl_->filename);
1673 void Buffer::saveCheckSum(string const & file) const
1675 if (fs::exists(file)) {
1676 pimpl_->timestamp_ = fs::last_write_time(file);
1677 pimpl_->checksum_ = sum(FileName(file));
1679 // in the case of save to a new file.
1680 pimpl_->timestamp_ = 0;
1681 pimpl_->checksum_ = 0;
1686 void Buffer::markClean() const
1688 if (!pimpl_->lyx_clean) {
1689 pimpl_->lyx_clean = true;
1692 // if the .lyx file has been saved, we don't need an
1694 pimpl_->bak_clean = true;
1698 void Buffer::markBakClean() const
1700 pimpl_->bak_clean = true;
1704 void Buffer::setUnnamed(bool flag)
1706 pimpl_->unnamed = flag;
1710 bool Buffer::isUnnamed() const
1712 return pimpl_->unnamed;
1716 // FIXME: this function should be moved to buffer_pimpl.C
1717 void Buffer::markDirty()
1719 if (pimpl_->lyx_clean) {
1720 pimpl_->lyx_clean = false;
1723 pimpl_->bak_clean = false;
1725 DepClean::iterator it = pimpl_->dep_clean.begin();
1726 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1728 for (; it != end; ++it)
1733 string const Buffer::fileName() const
1735 return pimpl_->filename.absFilename();
1739 string const & Buffer::filePath() const
1741 return params().filepath;
1745 bool Buffer::isReadonly() const
1747 return pimpl_->read_only;
1751 void Buffer::setParentName(string const & name)
1753 if (name == pimpl_->filename.absFilename())
1754 // Avoids recursive include.
1755 params().parentname.clear();
1757 params().parentname = name;
1761 Buffer const * Buffer::getMasterBuffer() const
1763 if (!params().parentname.empty()
1764 && theBufferList().exists(params().parentname)) {
1765 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1766 //We need to check if the parent is us...
1767 //FIXME RECURSIVE INCLUDE
1768 //This is not sufficient, since recursive includes could be downstream.
1769 if (buf && buf != this)
1770 return buf->getMasterBuffer();
1777 Buffer * Buffer::getMasterBuffer()
1779 if (!params().parentname.empty()
1780 && theBufferList().exists(params().parentname)) {
1781 Buffer * buf = theBufferList().getBuffer(params().parentname);
1782 //We need to check if the parent is us...
1783 //FIXME RECURSIVE INCLUDE
1784 //This is not sufficient, since recursive includes could be downstream.
1785 if (buf && buf != this)
1786 return buf->getMasterBuffer();
1793 MacroData const & Buffer::getMacro(docstring const & name) const
1795 return pimpl_->macros.get(name);
1799 bool Buffer::hasMacro(docstring const & name) const
1801 return pimpl_->macros.has(name);
1805 void Buffer::insertMacro(docstring const & name, MacroData const & data)
1807 MacroTable::globalMacros().insert(name, data);
1808 pimpl_->macros.insert(name, data);
1812 void Buffer::buildMacros()
1814 // Start with global table.
1815 pimpl_->macros = MacroTable::globalMacros();
1818 ParagraphList const & pars = text().paragraphs();
1819 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1820 //lyxerr << "searching main par " << i
1821 // << " for macro definitions" << std::endl;
1822 InsetList const & insets = pars[i].insetlist;
1823 InsetList::const_iterator it = insets.begin();
1824 InsetList::const_iterator end = insets.end();
1825 for ( ; it != end; ++it) {
1826 //lyxerr << "found inset code " << it->inset->lyxCode() << std::endl;
1827 if (it->inset->lyxCode() == Inset::MATHMACRO_CODE) {
1828 MathMacroTemplate const & mac
1829 = static_cast<MathMacroTemplate const &>(*it->inset);
1830 insertMacro(mac.name(), mac.asMacroData());
1837 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1840 //FIXME: This does not work for child documents yet.
1841 BOOST_ASSERT(code == Inset::CITE_CODE || code == Inset::REF_CODE);
1842 // Check if the label 'from' appears more than once
1843 vector<docstring> labels;
1845 if (code == Inset::CITE_CODE) {
1847 keys.fillWithBibKeys(this);
1848 BiblioInfo::const_iterator bit = keys.begin();
1849 BiblioInfo::const_iterator bend = keys.end();
1851 for (; bit != bend; ++bit)
1853 labels.push_back(bit->first);
1855 getLabelList(labels);
1857 if (std::count(labels.begin(), labels.end(), from) > 1)
1860 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1861 if (it->lyxCode() == code) {
1862 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1863 inset.replaceContents(to_utf8(from), to_utf8(to));
1869 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1870 pit_type par_end, bool full_source)
1872 OutputParams runparams(¶ms().encoding());
1873 runparams.nice = true;
1874 runparams.flavor = OutputParams::LATEX;
1875 runparams.linelen = lyxrc.plaintext_linelen;
1876 // No side effect of file copying and image conversion
1877 runparams.dryrun = true;
1881 os << "% " << _("Preview source code") << "\n\n";
1885 writeLaTeXSource(os, filePath(), runparams, true, true);
1887 writeDocBookSource(os, fileName(), runparams, false);
1890 runparams.par_begin = par_begin;
1891 runparams.par_end = par_end;
1892 if (par_begin + 1 == par_end)
1894 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1898 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1899 convert<docstring>(par_begin),
1900 convert<docstring>(par_end - 1))
1904 // output paragraphs
1906 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1909 docbookParagraphs(paragraphs(), *this, os, runparams);
1915 ErrorList const & Buffer::errorList(string const & type) const
1917 static ErrorList const emptyErrorList;
1918 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1919 if (I == pimpl_->errorLists.end())
1920 return emptyErrorList;
1926 ErrorList & Buffer::errorList(string const & type)
1928 return pimpl_->errorLists[type];
1932 void Buffer::structureChanged() const
1935 gui_->structureChanged();
1939 void Buffer::embeddingChanged() const
1942 gui_->embeddingChanged();
1946 void Buffer::errors(std::string const & err) const
1953 void Buffer::message(docstring const & msg) const
1960 void Buffer::busy(bool on) const
1967 void Buffer::readonly(bool on) const
1974 void Buffer::updateTitles() const
1977 gui_->updateTitles();
1981 void Buffer::resetAutosaveTimers() const
1984 gui_->resetAutosaveTimers();
1988 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
1997 class AutoSaveBuffer : public support::ForkedProcess {
2000 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
2001 : buffer_(buffer), fname_(fname) {}
2003 virtual boost::shared_ptr<ForkedProcess> clone() const
2005 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
2010 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
2011 from_utf8(fname_.absFilename())));
2012 return run(DontWait);
2016 virtual int generateChild();
2018 Buffer const & buffer_;
2023 int AutoSaveBuffer::generateChild()
2025 // tmp_ret will be located (usually) in /tmp
2026 // will that be a problem?
2027 pid_t const pid = fork(); // If you want to debug the autosave
2028 // you should set pid to -1, and comment out the
2030 if (pid == 0 || pid == -1) {
2031 // pid = -1 signifies that lyx was unable
2032 // to fork. But we will do the save
2034 bool failed = false;
2036 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2037 if (!tmp_ret.empty()) {
2038 buffer_.writeFile(tmp_ret);
2039 // assume successful write of tmp_ret
2040 if (!rename(tmp_ret, fname_)) {
2042 // most likely couldn't move between
2043 // filesystems unless write of tmp_ret
2044 // failed so remove tmp file (if it
2053 // failed to write/rename tmp_ret so try writing direct
2054 if (!buffer_.writeFile(fname_)) {
2055 // It is dangerous to do this in the child,
2056 // but safe in the parent, so...
2057 if (pid == -1) // emit message signal.
2058 buffer_.message(_("Autosave failed!"));
2061 if (pid == 0) { // we are the child so...
2071 // Perfect target for a thread...
2072 void Buffer::autoSave() const
2074 if (isBakClean() || isReadonly()) {
2075 // We don't save now, but we'll try again later
2076 resetAutosaveTimers();
2080 // emit message signal.
2081 message(_("Autosaving current document..."));
2083 // create autosave filename
2084 string fname = filePath();
2086 fname += onlyFilename(fileName());
2089 AutoSaveBuffer autosave(*this, FileName(fname));
2093 resetAutosaveTimers();
2097 /** Write a buffer to a new file name and rename the buffer
2098 according to the new file name.
2100 This function is e.g. used by menu callbacks and
2101 LFUN_BUFFER_WRITE_AS.
2103 If 'newname' is empty (the default), the user is asked via a
2104 dialog for the buffer's new name and location.
2106 If 'newname' is non-empty and has an absolute path, that is used.
2107 Otherwise the base directory of the buffer is used as the base
2108 for any relative path in 'newname'.
2111 bool Buffer::writeAs(string const & newname)
2113 string fname = fileName();
2114 string const oldname = fname;
2116 if (newname.empty()) { /// No argument? Ask user through dialog
2119 FileDialog fileDlg(_("Choose a filename to save document as"),
2120 LFUN_BUFFER_WRITE_AS,
2121 make_pair(_("Documents|#o#O"),
2122 from_utf8(lyxrc.document_path)),
2123 make_pair(_("Templates|#T#t"),
2124 from_utf8(lyxrc.template_path)));
2126 if (!support::isLyXFilename(fname))
2129 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2131 FileDialog::Result result =
2132 fileDlg.save(from_utf8(onlyPath(fname)),
2134 from_utf8(onlyFilename(fname)));
2136 if (result.first == FileDialog::Later)
2139 fname = to_utf8(result.second);
2144 // Make sure the absolute filename ends with appropriate suffix
2145 fname = makeAbsPath(fname).absFilename();
2146 if (!support::isLyXFilename(fname))
2150 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2152 if (fs::exists(FileName(fname).toFilesystemEncoding())) {
2153 docstring const file = makeDisplayPath(fname, 30);
2154 docstring text = bformat(_("The document %1$s already "
2155 "exists.\n\nDo you want to "
2156 "overwrite that document?"),
2158 int const ret = Alert::prompt(_("Overwrite document?"),
2159 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2165 // Ok, change the name of the buffer
2168 bool unnamed = isUnnamed();
2170 saveCheckSum(fname);
2173 setFileName(oldname);
2174 setUnnamed(unnamed);
2175 saveCheckSum(oldname);
2179 removeAutosaveFile(oldname);
2184 bool Buffer::menuWrite()
2187 LyX::ref().session().lastFiles().add(FileName(fileName()));
2191 // FIXME: we don't tell the user *WHY* the save failed !!
2193 docstring const file = makeDisplayPath(fileName(), 30);
2195 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2196 "Do you want to rename the document and "
2197 "try again?"), file);
2198 int const ret = Alert::prompt(_("Rename and save?"),
2199 text, 0, 1, _("&Rename"), _("&Cancel"));